domainkit 0.20.0 → 0.21.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.
- package/README.md +32 -1
- package/dist/Cloudflare.mjs +19 -10
- package/dist/Cloudflare.mjs.map +1 -1
- package/dist/Connect.mjs +30 -10
- package/dist/Connect.mjs.map +1 -1
- package/dist/Provider.mjs.map +1 -1
- package/dist/Server.mjs +121 -22
- package/dist/Server.mjs.map +1 -1
- package/dist/Vercel.mjs +20 -10
- package/dist/Vercel.mjs.map +1 -1
- package/dist/Verify.mjs +4 -3
- package/dist/Verify.mjs.map +1 -1
- package/dist/entry/client.mjs +1 -2
- package/dist/entry/server.mjs +1 -2
- package/dist/internal/callback.mjs +13 -0
- package/dist/internal/callback.mjs.map +1 -0
- package/dist/internal/config.mjs +19 -5
- package/dist/internal/config.mjs.map +1 -1
- package/dist/internal/provider-async.mjs +2 -0
- package/dist/internal/provider-async.mjs.map +1 -1
- package/dist/package.mjs +1 -1
- package/dist/types/Connect.d.ts +134 -0
- package/dist/types/Provider.d.ts +9 -0
- package/dist/types/Server.d.ts +45 -0
- package/dist/types/Verify.d.ts +5 -1
- package/dist/types/internal/callback.d.ts +4 -0
- package/dist/types/internal/config.d.ts +8 -1
- package/dist/types/internal/provider-async.d.ts +2 -0
- package/package.json +10 -10
package/dist/Connect.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Connect.mjs","names":["Errors.fail","Reason.InvalidInput","Storage.Service","Custody.Service","Providers.Service","Resolver.Service","Storage.Credential","Provider.decodeContext","Reason.Reconnect","Principal.Service","Reason.Forbidden","Storage.Authorization","Provider.encodeContext","DomainName.decode","Reason.NotFound","Storage.Attachment","candidates","DomainName.candidates","Provider.describeMethods","pkce","OAuth.pkce","Storage.Continuation","Errors.DomainKitError","Reason.Unauthenticated","Errors.decode"],"sources":["../src/Connect.ts"],"sourcesContent":["/**\n * Connect a provider, attach domains to it, and keep the credential alive. Interactive methods\n * (OAuth, integration) return a redirect and complete on callback; token methods connect in one\n * call. Reuse of an existing connection for a new domain is `attach`.\n */\nimport { Context, Data, DateTime, Duration, Effect, Layer, Option, Redacted, Schema } from \"effect\";\n\nimport * as Custody from \"./Custody.ts\";\nimport * as Errors from \"./internal/error.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as DomainName from \"./DomainName.ts\";\nimport { fresh } from \"./internal/ids.ts\";\nimport * as OAuth from \"./internal/oauth.ts\";\nimport * as Principal from \"./Principal.ts\";\nimport * as Provider from \"./Provider.ts\";\nimport * as Providers from \"./Providers.ts\";\nimport * as Resolver from \"./Resolver.ts\";\nimport * as Storage from \"./Storage.ts\";\n\ntype Fx<A> = Effect.Effect<A, Errors.DomainKitError, Principal.Service>;\n\nexport type Method = Data.TaggedEnum<{\n /** Values keyed by the provider's declared `auth.token.fields`. */\n Token: { readonly values: Provider.TokenValues };\n OAuth: { readonly returnTo?: string };\n Integration: { readonly returnTo?: string };\n}>;\nconst MethodEnum = Data.taggedEnum<Method>();\n/** `token` accepts a lone string (`{ token }`) or a record; every value is wrapped in `Redacted`. */\nexport const Method = {\n ...MethodEnum,\n token: (\n values: string | Readonly<Record<string, Redacted.Redacted<string> | string | undefined>>,\n ): Method =>\n MethodEnum.Token({\n values:\n typeof values === \"string\"\n ? { token: Redacted.make(values) }\n : Object.fromEntries(\n Object.entries(values).map(([name, value]) => [\n name,\n typeof value === \"string\" ? Redacted.make(value) : value,\n ]),\n ),\n }),\n oauth: (options: { readonly returnTo?: string } = {}): Method => MethodEnum.OAuth(options),\n integration: (options: { readonly returnTo?: string } = {}): Method =>\n MethodEnum.Integration(options),\n} as const;\n\n/** Everything the UI needs about a domain's provider state. */\nexport interface Snapshot {\n readonly domain: string;\n readonly attachment: Storage.Attachment | null;\n readonly connection: Storage.Connection | null;\n readonly authorization: Pick<\n Storage.Authorization,\n \"id\" | \"provider\" | \"method\" | \"capabilities\" | \"revocation\" | \"label\"\n > | null;\n readonly lastReceiptId: string | null;\n /** How many domains this domain's connection serves, including this one; `0` without one. */\n readonly connectionDomains: number;\n /** Other connections of this owner that could serve this domain. */\n readonly reusable: ReadonlyArray<{\n readonly connection: Storage.Connection;\n readonly provider: string;\n readonly method: Storage.AuthMethod;\n }>;\n readonly providers: ReadonlyArray<{\n readonly id: string;\n readonly name: string;\n readonly methods: ReadonlyArray<Provider.MethodDescriptor>;\n }>;\n}\n\nexport type Started = Data.TaggedEnum<{\n Connected: {\n readonly connection: Storage.Connection;\n readonly attachment: Storage.Attachment | null;\n };\n Redirect: { readonly authorizationUrl: string; readonly continuationId: string };\n SelectionRequired: {\n readonly connection: Storage.Connection;\n readonly candidates: ReadonlyArray<Provider.Target>;\n };\n}>;\nexport const Started = Data.taggedEnum<Started>();\n\n/** `attach` either attached the domain or needs the caller to pick a zone. */\nexport type Selection = Data.TaggedEnum<{\n SelectionRequired: { readonly candidates: ReadonlyArray<Provider.Target> };\n}>;\nexport const Selection = Data.taggedEnum<Selection>();\n\nexport type Attached = Storage.Attachment | Selection;\n\nexport type Refreshed = \"current\" | \"refreshed\" | \"reconnect\";\n\n/** Which of the principal's connections serves a domain, from nameserver evidence and zone lists. */\nexport type Discovery = Data.TaggedEnum<{\n Resolved: { readonly connectionId: string; readonly target: Provider.Target };\n SelectionRequired: {\n readonly candidates: ReadonlyArray<{\n readonly connectionId: string;\n readonly target: Provider.Target;\n }>;\n };\n /**\n * No connection reaches the domain. `host` names the one registered provider whose declared\n * nameserver suffixes cover every authoritative nameserver; `null` when none or several do.\n */\n NotFound: {\n readonly nameservers: ReadonlyArray<string>;\n readonly host: { readonly provider: string } | null;\n };\n}>;\nexport const Discovery = Data.taggedEnum<Discovery>();\n\n/** One zone a connection reaches, with the connection that reaches it. */\nexport interface Zone {\n readonly connectionId: string;\n readonly provider: string;\n readonly target: Provider.Target;\n}\n\n/**\n * A connection's standing in a zone listing. `reconnect` is a credential the provider turned down:\n * the connection is still the owner's, and the other connections' zones came back regardless.\n */\nexport interface ZoneConnection {\n readonly connectionId: string;\n readonly provider: string;\n /** What the provider called the account at connect time; `null` when it named none. */\n readonly label: string | null;\n readonly status: \"connected\" | \"reconnect\";\n}\n\n/** Every zone the principal's connections reach, and how each connection is standing. */\nexport interface Zones {\n /** Ordered by zone, then by connection, so two listings of the same state read the same. */\n readonly zones: ReadonlyArray<Zone>;\n readonly connections: ReadonlyArray<ZoneConnection>;\n /** Every provider a customer could connect from here, so an offer needs no second call. */\n readonly providers: Snapshot[\"providers\"];\n}\n\nexport interface Interface {\n readonly inspect: (domain: string) => Fx<Snapshot>;\n /**\n * Every zone the principal's connections can serve, from `Provider.Session.listTargets`. A\n * connection whose credential the provider turned down is marked `reconnect` in `connections`\n * and contributes no zones; the rest still list.\n */\n readonly zones: (options?: { readonly provider?: string }) => Fx<Zones>;\n /**\n * Resolve the domain's authoritative nameservers, then match them against the zones each of\n * the principal's connections can reach: the closest zone wins, a decisive nameserver match\n * breaks ties, and anything else needs a selection.\n */\n readonly discover: (domain: string) => Fx<Discovery>;\n /** Connect a provider and, when `domain` is given, attach it. Token methods finish here. */\n readonly start: (input: {\n readonly provider: string;\n readonly method: Method;\n readonly domain?: string;\n readonly callbackUrl?: string;\n }) => Fx<Started>;\n /**\n * Replace the credential behind a connection the owner already holds, keeping the connection and\n * every domain attached to it. This is what a customer means by reconnecting: starting again\n * would mint a second account and leave the domains on the one the provider turned down. The\n * provider is the connection's own; a credential for a different account is the customer's\n * choice, and the zone check on every session catches a zone it can no longer reach.\n */\n readonly reconnect: (input: {\n readonly connectionId: string;\n readonly method: Method;\n readonly callbackUrl?: string;\n }) => Fx<Started>;\n /** Finish an interactive connection from the provider callback (the full callback URL). */\n readonly complete: (input: {\n readonly continuationId: string;\n readonly callbackUrl: string;\n }) => Fx<Started>;\n readonly attach: (input: {\n readonly connectionId: string;\n readonly domain: string;\n readonly target?: Provider.Target;\n }) => Fx<Attached>;\n /** Records stay in DNS; DomainKit forgets the domain. Use `Cleanup` first to remove records. */\n readonly detach: (attachmentId: string) => Fx<void>;\n /** Detach every domain and revoke the credential at the provider when the method supports it. */\n readonly disconnect: (connectionId: string) => Fx<void>;\n /** Refresh a near-expiry credential, single-flighted. Called automatically before provider use. */\n readonly refresh: (connectionId: string) => Fx<Refreshed>;\n /** Live provider session for an attachment; used by Provision, Cleanup, Verify. */\n readonly session: (\n attachmentId: string,\n ) => Fx<{ readonly session: Provider.Session; readonly target: Provider.Target }>;\n}\n\nexport class Service extends Context.Service<Service, Interface>()(\"@domainkit/Connect\") {}\n\n/** Which owner-scoped reuse is allowed. Default: any connection of the same owner may serve any of its domains. */\nexport interface PolicyShape {\n readonly allowReuse: (input: {\n readonly connection: Storage.Connection;\n readonly domain: string;\n }) => Effect.Effect<boolean, never, Principal.Service>;\n /** Refresh this long before expiry. Default 10 minutes. */\n readonly refreshBeforeMs: number;\n /** Continuation lifetime. Default 15 minutes. */\n readonly continuationTtlMs: number;\n}\nexport const defaults: PolicyShape = {\n allowReuse: () => Effect.succeed(true),\n refreshBeforeMs: 10 * 60_000,\n continuationTtlMs: 15 * 60_000,\n};\nexport class Policy extends Context.Reference<PolicyShape>(\"@domainkit/Connect/Policy\", {\n defaultValue: () => defaults,\n}) {}\n\n// ---------------------------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------------------------\n\nconst Payload = Schema.Struct({\n method: Schema.Literals([\"oauth\", \"integration\"]),\n codeVerifier: Schema.NullOr(Schema.String),\n callbackUrl: Schema.String,\n domain: Schema.NullOr(Schema.String),\n /** Set when the flow is re-crediting a connection rather than making one. */\n connectionId: Schema.NullOr(Schema.String),\n});\n\nconst Target = Schema.Struct({\n zone: Schema.String,\n context: Schema.Unknown,\n label: Schema.String,\n});\n\nconst invalid = (message: string, field?: string) =>\n Errors.fail(new Reason.InvalidInput({ message, ...(field === undefined ? {} : { field }) }));\n\n/** Nameserver hostnames compare case-insensitively and without a trailing dot. */\nconst normalize = (name: string) => name.toLowerCase().replace(/\\.$/, \"\");\n\nexport const make: Effect.Effect<\n Interface,\n never,\n Storage.Service | Custody.Service | Providers.Service | Resolver.Service\n> = Effect.gen(function* () {\n const storage = yield* Storage.Service;\n const custody = yield* Custody.Service;\n const providers = yield* Providers.Service;\n const resolver = yield* Resolver.Service;\n\n const seal = (issued: Provider.IssuedCredential) =>\n Effect.gen(function* () {\n const ciphertext = yield* custody.seal(issued.secret);\n return new Storage.Credential({\n ciphertext,\n expiresAt: issued.expiresAt,\n rotatedAt: yield* DateTime.now,\n });\n });\n\n /** Plaintext credential with the context decoded from its stored envelope. */\n const open = (authorization: Storage.Authorization): Fx<Provider.Credential> =>\n Effect.gen(function* () {\n const definition = yield* providers.get(authorization.provider);\n const row = yield* storage.authorizations.credential(authorization.id);\n const secret = yield* custody.open(row.ciphertext);\n const context = yield* Provider.decodeContext(definition, authorization.context);\n return { secret, context };\n });\n\n const refresherFor = (definition: Provider.Definition, method: Storage.AuthMethod) =>\n method === \"oauth\"\n ? definition.auth.oauth?.refresh\n : method === \"integration\"\n ? definition.auth.integration?.refresh\n : undefined;\n\n const revokerFor = (definition: Provider.Definition, method: Storage.AuthMethod) =>\n method === \"oauth\"\n ? definition.auth.oauth?.revoke\n : method === \"integration\"\n ? definition.auth.integration?.revoke\n : undefined;\n\n const revokeAt = (authorization: Storage.Authorization): Fx<void> =>\n Effect.gen(function* () {\n const definition = yield* providers.get(authorization.provider);\n const revoke = revokerFor(definition, authorization.method);\n if (revoke === undefined) return;\n const credential = yield* open(authorization);\n yield* revoke(credential);\n });\n\n /** Finish revocations a crash interrupted; best effort, never blocks the caller. */\n const recover = storage.authorizations.recoverRevocations(revokeAt).pipe(Effect.ignore);\n\n const refresh: Interface[\"refresh\"] = (connectionId) =>\n Effect.gen(function* () {\n const policy = yield* Policy;\n const connection = yield* storage.connections.get(connectionId);\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const definition = yield* providers.get(authorization.provider);\n const refresher = refresherFor(definition, authorization.method);\n const dueAt = (row: Storage.Credential, now: DateTime.Utc) =>\n row.expiresAt !== null &&\n DateTime.toEpochMillis(row.expiresAt) - DateTime.toEpochMillis(now) <=\n policy.refreshBeforeMs;\n const expired = (row: Storage.Credential, now: DateTime.Utc) =>\n row.expiresAt !== null &&\n DateTime.toEpochMillis(row.expiresAt) <= DateTime.toEpochMillis(now);\n const current = yield* storage.authorizations.credential(authorization.id);\n const now = yield* DateTime.now;\n if (!dueAt(current, now)) return \"current\" as const;\n if (refresher === undefined)\n return expired(current, now) ? (\"reconnect\" as const) : (\"current\" as const);\n return yield* storage\n .withLock(\n `refresh:${authorization.id}`,\n Effect.gen(function* () {\n // Disconnect's two-phase revocation does not take this lock: re-read the row here\n // so a revocation that started meanwhile skips the refresh instead of racing it.\n const live = yield* storage.authorizations.get(authorization.id).pipe(\n Effect.map(Option.some),\n Effect.catchIf(\n (error) => error.reason._tag === \"NotFound\",\n () => Effect.succeed(Option.none<Storage.Authorization>()),\n ),\n );\n if (Option.isNone(live) || live.value.revocation !== \"active\") {\n return \"reconnect\" as const;\n }\n const latest = yield* storage.authorizations.credential(authorization.id);\n if (!dueAt(latest, yield* DateTime.now)) return \"current\" as const;\n const secret = yield* custody.open(latest.ciphertext);\n const context = yield* Provider.decodeContext(definition, live.value.context);\n const issued = yield* refresher({ secret, context });\n // A revocation that completed while the provider was issuing leaves no row to\n // rotate; revoke the fresh credential so it does not stay live without a home.\n yield* storage.authorizations\n .rotate(authorization.id, yield* seal(issued))\n .pipe(\n Effect.tapError((error) =>\n error.reason._tag === \"NotFound\"\n ? (revokerFor(definition, authorization.method)?.(issued) ?? Effect.void).pipe(\n Effect.ignore,\n )\n : Effect.void,\n ),\n );\n return \"refreshed\" as const;\n }),\n )\n .pipe(\n Effect.catchIf(\n (error) => error.reason._tag === \"Busy\" && !expired(current, now),\n () => Effect.succeed(\"current\" as const),\n ),\n Effect.catchIf(\n (error) => error.reason._tag === \"Unauthenticated\",\n () => Effect.succeed(\"reconnect\" as const),\n ),\n );\n });\n\n const sessionFor = (connection: Storage.Connection): Fx<Provider.Session> =>\n Effect.gen(function* () {\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const reconnect = Errors.fail(\n new Reason.Reconnect({\n provider: authorization.provider,\n connectionId: connection.id,\n }),\n );\n if (authorization.revocation !== \"active\") return yield* reconnect;\n if ((yield* refresh(connection.id)) === \"reconnect\") return yield* reconnect;\n const definition = yield* providers.get(authorization.provider);\n return definition.session(yield* open(authorization));\n });\n\n const connectWith = (input: {\n readonly definition: Provider.Definition;\n readonly method: Storage.AuthMethod;\n readonly issued: Provider.IssuedCredential;\n readonly requiredCapabilities: ReadonlyArray<Storage.Capability>;\n }): Fx<{ readonly connection: Storage.Connection; readonly session: Provider.Session }> =>\n Effect.gen(function* () {\n const principal = yield* Principal.Service;\n const session = input.definition.session(input.issued);\n const held = yield* session.capabilities();\n const missing = input.requiredCapabilities.filter((capability) => !held.includes(capability));\n if (missing.length > 0) {\n return yield* Errors.fail(\n new Reason.Forbidden({\n message: `${input.definition.name} credential lacks ${missing.join(\", \")}`,\n }),\n );\n }\n const authorization = new Storage.Authorization({\n id: yield* fresh(\"auth\"),\n ownerId: principal.ownerId,\n provider: input.definition.id,\n method: input.method,\n capabilities: held,\n context: yield* Provider.encodeContext(input.definition, input.issued.context),\n label: input.issued.label ?? null,\n revocation: \"active\",\n createdBy: principal.actorId,\n createdAt: yield* DateTime.now,\n });\n yield* storage.authorizations.upsert({\n authorization,\n credential: yield* seal(input.issued),\n });\n const connection = yield* storage.connections.create(authorization.id);\n return { connection, session };\n });\n\n /**\n * Put a fresh credential behind an authorization the owner already has. The row keeps its id, so\n * every connection over it, and every domain on those connections, stays exactly where it is.\n */\n const replaceWith = (input: {\n readonly definition: Provider.Definition;\n readonly authorizationId: string;\n readonly method: Storage.AuthMethod;\n readonly issued: Provider.IssuedCredential;\n readonly requiredCapabilities: ReadonlyArray<Storage.Capability>;\n }): Fx<void> =>\n Effect.gen(function* () {\n const principal = yield* Principal.Service;\n const session = input.definition.session(input.issued);\n const held = yield* session.capabilities();\n const missing = input.requiredCapabilities.filter((capability) => !held.includes(capability));\n if (missing.length > 0) {\n return yield* Errors.fail(\n new Reason.Forbidden({\n message: `${input.definition.name} credential lacks ${missing.join(\", \")}`,\n }),\n );\n }\n const authorization = new Storage.Authorization({\n id: input.authorizationId,\n ownerId: principal.ownerId,\n provider: input.definition.id,\n method: input.method,\n capabilities: held,\n context: yield* Provider.encodeContext(input.definition, input.issued.context),\n label: input.issued.label ?? null,\n revocation: \"active\",\n createdBy: principal.actorId,\n createdAt: yield* DateTime.now,\n });\n yield* storage.authorizations.upsert({\n authorization,\n credential: yield* seal(input.issued),\n expectedId: input.authorizationId,\n });\n });\n\n const attachWith = (input: {\n readonly definition: Provider.Definition;\n readonly connection: Storage.Connection;\n readonly session: Provider.Session;\n readonly domain: string;\n readonly target?: Provider.Target;\n }): Fx<Attached> =>\n Effect.gen(function* () {\n const domain = yield* DomainName.decode(input.domain);\n const existing = yield* storage.attachments.byDomain(domain);\n if (Option.isSome(existing)) {\n if (existing.value.connectionId === input.connection.id) return existing.value;\n return yield* invalid(`${domain} is already attached to another connection`, \"domain\");\n }\n const resolution: Provider.Resolution =\n input.target === undefined\n ? yield* input.session.resolveTarget(domain)\n : Provider.Resolution.Resolved({ target: input.target });\n switch (resolution._tag) {\n case \"SelectionRequired\":\n return Selection.SelectionRequired({ candidates: resolution.candidates });\n case \"NotFound\":\n return yield* Errors.fail(new Reason.NotFound({ entity: \"zone\", id: domain }));\n case \"Resolved\":\n return yield* storage.attachments.create({\n connectionId: input.connection.id,\n domain,\n zone: resolution.target.zone,\n label: resolution.target.label,\n target: {\n ...resolution.target,\n context: yield* Provider.encodeContext(input.definition, resolution.target.context),\n },\n });\n }\n });\n\n const started = (connection: Storage.Connection, attached: Attached | null): Started =>\n attached === null\n ? Started.Connected({ connection, attachment: null })\n : attached instanceof Storage.Attachment\n ? Started.Connected({ connection, attachment: attached })\n : Started.SelectionRequired({ connection, candidates: attached.candidates });\n\n /** Authoritative nameservers for the closest zone that answers an NS query. */\n const authoritative = (candidates: ReadonlyArray<DomainName.Model>) =>\n Effect.gen(function* () {\n for (const zone of candidates) {\n const outcomes = yield* resolver.resolve(zone, \"NS\");\n const names = outcomes.flatMap((outcome) =>\n outcome._tag === \"Answered\"\n ? outcome.answer.records.flatMap((record) =>\n record._tag === \"NS\" ? [record.nameserver] : [],\n )\n : [],\n );\n if (names.length > 0) return [...new Set(names)].sort();\n }\n return [] as ReadonlyArray<string>;\n });\n\n const discover: Interface[\"discover\"] = (input) =>\n Effect.gen(function* () {\n const domain = yield* DomainName.decode(input);\n const candidates = DomainName.candidates(domain);\n const nameservers = yield* authoritative(candidates);\n const reachable: Array<{\n readonly connectionId: string;\n readonly target: Provider.Target;\n }> = [];\n for (const connection of yield* storage.connections.list()) {\n const targets = yield* sessionFor(connection).pipe(\n Effect.flatMap((session) => session.listTargets()),\n Effect.catch(() => Effect.succeed([] as ReadonlyArray<Provider.Target>)),\n );\n for (const target of targets) {\n if (candidates.includes(target.zone as DomainName.Model)) {\n reachable.push({ connectionId: connection.id, target });\n }\n }\n }\n for (const zone of candidates) {\n const matches = reachable\n .filter(({ target }) => target.zone === zone)\n .sort((left, right) => left.connectionId.localeCompare(right.connectionId));\n if (matches.length === 0) continue;\n const [only] = matches;\n if (matches.length === 1 && only !== undefined) return Discovery.Resolved(only);\n const decisive = matches.filter(({ target }) => {\n const configured = new Set((target.nameservers ?? []).map(normalize));\n return nameservers.length > 0 && nameservers.every((name) => configured.has(name));\n });\n const [winner] = decisive;\n if (decisive.length === 1 && winner !== undefined) return Discovery.Resolved(winner);\n return Discovery.SelectionRequired({ candidates: matches });\n }\n return Discovery.NotFound({ nameservers, host: hostOf(nameservers) });\n });\n\n /** The one registered provider whose declared suffixes cover every nameserver, else `null`. */\n const hostOf = (nameservers: ReadonlyArray<string>): { readonly provider: string } | null => {\n if (nameservers.length === 0) return null;\n const names = nameservers.map(normalize);\n const hosts = providers.list().filter((definition) => {\n const suffixes = (definition.nameservers ?? []).map(normalize);\n return (\n suffixes.length > 0 &&\n names.every((name) =>\n suffixes.some((suffix) => name === suffix || name.endsWith(`.${suffix}`)),\n )\n );\n });\n const [host] = hosts;\n return hosts.length === 1 && host !== undefined ? { provider: host.id } : null;\n };\n\n const zones: Interface[\"zones\"] = (options = {}) =>\n Effect.gen(function* () {\n const reachable: Array<Zone> = [];\n const connections: Array<ZoneConnection> = [];\n const filter = options.provider === undefined ? undefined : { provider: options.provider };\n for (const connection of yield* storage.connections.list(filter)) {\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n // A credential the provider turned down is one connection's problem, not the listing's:\n // it is reported as needing a reconnect and every other connection still answers. Any\n // other failure is the listing's own, so it is raised rather than dressed up as an\n // authorization the customer has to grant again.\n const targets = yield* sessionFor(connection).pipe(\n Effect.flatMap((session) => session.listTargets()),\n Effect.map(Option.some),\n Effect.catchIf(\n (error) =>\n error.reason._tag === \"Reconnect\" ||\n error.reason._tag === \"Unauthenticated\" ||\n error.reason._tag === \"Forbidden\",\n () => Effect.succeed(Option.none<ReadonlyArray<Provider.Target>>()),\n ),\n );\n connections.push({\n connectionId: connection.id,\n provider: authorization.provider,\n label: authorization.label,\n status: Option.isNone(targets) ? \"reconnect\" : \"connected\",\n });\n if (Option.isNone(targets)) continue;\n for (const target of targets.value) {\n reachable.push({\n connectionId: connection.id,\n provider: authorization.provider,\n target,\n });\n }\n }\n return {\n zones: reachable.sort(\n (left, right) =>\n left.target.zone.localeCompare(right.target.zone) ||\n left.connectionId.localeCompare(right.connectionId),\n ),\n connections,\n providers: providers.list().map((definition) => ({\n id: definition.id,\n name: definition.name,\n methods: Provider.describeMethods(definition),\n })),\n };\n });\n\n const inspect: Interface[\"inspect\"] = (input) =>\n Effect.gen(function* () {\n const domain = yield* DomainName.decode(input);\n const policy = yield* Policy;\n const attachment = Option.getOrNull(yield* storage.attachments.byDomain(domain));\n const connection =\n attachment === null ? null : yield* storage.connections.get(attachment.connectionId);\n const authorization =\n connection === null ? null : yield* storage.authorizations.get(connection.authorizationId);\n const latest =\n attachment === null\n ? Option.none()\n : yield* storage.attempts.latest(attachment.id, \"provisioning\");\n // Letting a connection go takes every domain on it, so the UI has to be able to say how many.\n const attached = connection === null ? [] : yield* storage.attachments.list(connection.id);\n const reusable: Array<Snapshot[\"reusable\"][number]> = [];\n for (const candidate of yield* storage.connections.list()) {\n if (candidate.id === connection?.id) continue;\n if (!(yield* policy.allowReuse({ connection: candidate, domain }))) continue;\n const candidateAuthorization = yield* storage.authorizations.get(candidate.authorizationId);\n if (candidateAuthorization.revocation !== \"active\") continue;\n reusable.push({\n connection: candidate,\n provider: candidateAuthorization.provider,\n method: candidateAuthorization.method,\n });\n }\n return {\n domain,\n attachment,\n connection,\n authorization:\n authorization === null\n ? null\n : {\n id: authorization.id,\n provider: authorization.provider,\n method: authorization.method,\n capabilities: authorization.capabilities,\n revocation: authorization.revocation,\n label: authorization.label,\n },\n lastReceiptId: Option.isSome(latest) ? (latest.value.receipt?.id ?? null) : null,\n connectionDomains: attached.length,\n reusable,\n providers: providers.list().map((definition) => ({\n id: definition.id,\n name: definition.name,\n methods: Provider.describeMethods(definition),\n })),\n };\n });\n\n const start: Interface[\"start\"] = (input) =>\n Effect.gen(function* () {\n yield* recover;\n const definition = yield* providers.get(input.provider);\n if (input.domain !== undefined) yield* DomainName.decode(input.domain);\n switch (input.method._tag) {\n case \"Token\": {\n const token = definition.auth.token;\n if (token === undefined) {\n return yield* invalid(`${definition.name} does not accept tokens`, \"method\");\n }\n const issued = yield* token.authenticate(input.method.values);\n const { connection, session } = yield* connectWith({\n definition,\n method: \"token\",\n issued,\n requiredCapabilities: token.requiredCapabilities,\n });\n const attached =\n input.domain === undefined\n ? null\n : yield* attachWith({ definition, connection, session, domain: input.domain });\n return started(connection, attached);\n }\n case \"OAuth\":\n case \"Integration\":\n return yield* redirectFor({\n definition,\n method: input.method,\n callbackUrl: input.callbackUrl,\n domain: input.domain ?? null,\n connectionId: null,\n });\n }\n });\n\n /** The redirect an interactive method answers with, and the continuation that finishes it. */\n const redirectFor = (input: {\n readonly definition: Provider.Definition;\n readonly method: Extract<Method, { readonly _tag: \"OAuth\" | \"Integration\" }>;\n readonly callbackUrl: string | undefined;\n readonly domain: string | null;\n readonly connectionId: string | null;\n }): Fx<Started> =>\n Effect.gen(function* () {\n const principal = yield* Principal.Service;\n const policy = yield* Policy;\n const interactive =\n input.method._tag === \"OAuth\"\n ? input.definition.auth.oauth\n : input.definition.auth.integration;\n if (interactive === undefined) {\n return yield* invalid(\n `${input.definition.name} does not offer ${input.method._tag.toLowerCase()} connections`,\n \"method\",\n );\n }\n if (input.callbackUrl === undefined) {\n return yield* invalid(\"Interactive connections need a callbackUrl\", \"callbackUrl\");\n }\n const callbackUrl = input.callbackUrl;\n const continuationId = yield* fresh(\"cont\");\n const pkce = input.method._tag === \"OAuth\" ? yield* OAuth.pkce() : null;\n const redirect =\n input.method._tag === \"OAuth\"\n ? yield* (interactive as Provider.OAuthAuth).start({\n state: continuationId,\n callbackUrl,\n codeChallenge: pkce?.codeChallenge ?? \"\",\n })\n : yield* (interactive as Provider.IntegrationAuth).start({\n state: continuationId,\n callbackUrl,\n });\n yield* storage.continuations.put(\n new Storage.Continuation({\n id: continuationId,\n ownerId: principal.ownerId,\n actorId: principal.actorId,\n provider: input.definition.id,\n payload: Schema.encodeSync(Payload)({\n method: input.method._tag === \"OAuth\" ? \"oauth\" : \"integration\",\n codeVerifier: pkce?.codeVerifier ?? null,\n callbackUrl,\n domain: input.domain,\n connectionId: input.connectionId,\n }),\n returnTo: input.method.returnTo ?? null,\n expiresAt: DateTime.addDuration(\n yield* DateTime.now,\n Duration.millis(policy.continuationTtlMs),\n ),\n }),\n );\n return Started.Redirect({ authorizationUrl: redirect.authorizationUrl, continuationId });\n });\n\n const reconnect: Interface[\"reconnect\"] = (input) =>\n Effect.gen(function* () {\n yield* recover;\n const connection = yield* storage.connections.get(input.connectionId);\n const existing = yield* storage.authorizations.get(connection.authorizationId);\n const definition = yield* providers.get(existing.provider);\n switch (input.method._tag) {\n case \"Token\": {\n const token = definition.auth.token;\n if (token === undefined) {\n return yield* invalid(`${definition.name} does not accept tokens`, \"method\");\n }\n const issued = yield* token.authenticate(input.method.values);\n yield* replaceWith({\n definition,\n authorizationId: existing.id,\n method: \"token\",\n issued,\n requiredCapabilities: token.requiredCapabilities,\n });\n return Started.Connected({ connection, attachment: null });\n }\n case \"OAuth\":\n case \"Integration\":\n return yield* redirectFor({\n definition,\n method: input.method,\n callbackUrl: input.callbackUrl,\n domain: null,\n connectionId: connection.id,\n });\n }\n });\n\n const complete: Interface[\"complete\"] = (input) =>\n Effect.gen(function* () {\n // The continuation is spent only after the credential and connection are persisted, so a\n // failure before that (bad callback, provider outage, storage outage) leaves the flow\n // retryable. Concurrent callbacks for the same continuation serialize on a lock; the\n // loser sees Busy or NotFound. A provider code is single-use, so a retry after the\n // provider already redeemed it fails Unauthenticated and the customer starts over.\n const callback = yield* Effect.try({\n try: () => new URL(input.callbackUrl),\n catch: () =>\n new Errors.DomainKitError({\n reason: new Reason.InvalidInput({\n message: \"callbackUrl is not a URL\",\n field: \"callbackUrl\",\n }),\n }),\n });\n const params = Object.fromEntries(callback.searchParams);\n const unauthenticated = (message: string) =>\n Errors.fail(new Reason.Unauthenticated({ message }));\n if (params.error !== undefined) {\n return yield* unauthenticated(`Provider returned ${params.error}`);\n }\n if (params.state !== input.continuationId) {\n return yield* unauthenticated(\"Callback state does not match the continuation\");\n }\n const code = params.code;\n if (code === undefined) return yield* unauthenticated(\"Callback has no code\");\n return yield* storage.withLock(\n `continuation:${input.continuationId}`,\n Effect.gen(function* () {\n const continuation = yield* storage.continuations.get(input.continuationId);\n const payload = yield* Errors.decode(Payload, continuation.payload, \"continuation\");\n const definition = yield* providers.get(continuation.provider);\n const issued =\n payload.method === \"oauth\"\n ? yield* Effect.gen(function* () {\n const oauth = definition.auth.oauth;\n if (oauth === undefined || payload.codeVerifier === null) {\n return yield* invalid(\n `${definition.name} does not offer oauth connections`,\n \"method\",\n );\n }\n return yield* oauth.complete({\n code,\n callbackUrl: payload.callbackUrl,\n codeVerifier: payload.codeVerifier,\n params,\n });\n })\n : yield* Effect.gen(function* () {\n const integration = definition.auth.integration;\n if (integration === undefined) {\n return yield* invalid(\n `${definition.name} does not offer integration connections`,\n \"method\",\n );\n }\n return yield* integration.complete({\n code,\n callbackUrl: payload.callbackUrl,\n params,\n });\n });\n if (payload.connectionId !== null) {\n const held = yield* storage.connections.get(payload.connectionId);\n const existing = yield* storage.authorizations.get(held.authorizationId);\n yield* replaceWith({\n definition,\n authorizationId: existing.id,\n method: payload.method,\n issued,\n requiredCapabilities: [],\n });\n yield* storage.continuations.consume(input.continuationId).pipe(Effect.ignore);\n return Started.Connected({ connection: held, attachment: null });\n }\n const { connection, session } = yield* connectWith({\n definition,\n method: payload.method,\n issued,\n requiredCapabilities: [],\n });\n const attached =\n payload.domain === null\n ? null\n : yield* attachWith({ definition, connection, session, domain: payload.domain });\n // The connection is durable at this point. Whatever happens to the continuation\n // now (expired, already spent, or a storage failure while spending it) no longer\n // changes the outcome: the row expires on its own and a replayed callback fails at\n // the provider, so the persisted result is returned.\n yield* storage.continuations.consume(input.continuationId).pipe(Effect.ignore);\n return started(connection, attached);\n }),\n );\n });\n\n const attach: Interface[\"attach\"] = (input) =>\n Effect.gen(function* () {\n const connection = yield* storage.connections.get(input.connectionId);\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const definition = yield* providers.get(authorization.provider);\n const session = yield* sessionFor(connection);\n return yield* attachWith({\n definition,\n connection,\n session,\n domain: input.domain,\n ...(input.target === undefined ? {} : { target: input.target }),\n });\n });\n\n const disconnect: Interface[\"disconnect\"] = (connectionId) =>\n Effect.gen(function* () {\n yield* recover;\n const connection = yield* storage.connections.get(connectionId);\n const attachments = yield* storage.attachments.list(connectionId);\n yield* Effect.forEach(attachments, (attachment) => storage.attachments.remove(attachment.id));\n yield* storage.connections.remove(connectionId);\n const remaining = (yield* storage.connections.list()).some(\n (candidate) => candidate.authorizationId === connection.authorizationId,\n );\n if (remaining) return;\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n yield* storage.authorizations.revoke(authorization.id, revokeAt(authorization));\n });\n\n const session: Interface[\"session\"] = (attachmentId) =>\n Effect.gen(function* () {\n const attachment = yield* storage.attachments.get(attachmentId);\n const connection = yield* storage.connections.get(attachment.connectionId);\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const definition = yield* providers.get(authorization.provider);\n const stored = yield* Errors.decode(Target, attachment.target, \"target\");\n const target: Provider.Target = {\n ...stored,\n context: yield* Provider.decodeContext(definition, stored.context),\n };\n const built = yield* sessionFor(connection);\n // The zone must still be reachable by this credential: it may have moved account, been\n // deleted, or become a kind the provider cannot host records in.\n const reachable = yield* built.listTargets();\n if (!reachable.some((candidate) => candidate.zone === target.zone)) {\n return yield* Errors.fail(new Reason.NotFound({ entity: \"zone\", id: target.zone }));\n }\n return { session: built, target };\n });\n\n return {\n inspect,\n zones,\n discover,\n start,\n reconnect,\n complete,\n attach,\n detach: (attachmentId) => storage.attachments.remove(attachmentId),\n disconnect,\n refresh,\n session,\n };\n});\n\nexport const layer: Layer.Layer<\n Service,\n never,\n Storage.Service | Custody.Service | Providers.Service | Resolver.Service\n> = Layer.effect(Service)(make);\n\nconst accessor =\n <Args extends ReadonlyArray<unknown>, A>(\n pick: (service: Interface) => (...args: Args) => Fx<A>,\n ): ((...args: Args) => Effect.Effect<A, Errors.DomainKitError, Principal.Service | Service>) =>\n (...args) =>\n Effect.flatMap(Service, (service) => pick(service)(...args));\n\nexport const inspect = accessor((service) => service.inspect);\nexport const zones = accessor((service) => service.zones);\nexport const discover = accessor((service) => service.discover);\nexport const start = accessor((service) => service.start);\nexport const reconnect = accessor((service) => service.reconnect);\nexport const complete = accessor((service) => service.complete);\nexport const attach = accessor((service) => service.attach);\nexport const detach = accessor((service) => service.detach);\nexport const disconnect = accessor((service) => service.disconnect);\nexport const refresh = accessor((service) => service.refresh);\nexport const session = accessor((service) => service.session);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,MAAM,aAAa,KAAK,WAAmB;;AAE3C,MAAa,SAAS;CACpB,GAAG;CACH,QACE,WAEA,WAAW,MAAM,EACf,QACE,OAAO,WAAW,WACd,EAAE,OAAO,SAAS,KAAK,MAAM,EAAE,IAC/B,OAAO,YACL,OAAO,QAAQ,MAAM,CAAC,CAAC,KAAK,CAAC,MAAM,WAAW,CAC5C,MACA,OAAO,UAAU,WAAW,SAAS,KAAK,KAAK,IAAI,KACrD,CAAC,CACH,EACR,CAAC;CACH,QAAQ,UAA0C,CAAC,MAAc,WAAW,MAAM,OAAO;CACzF,cAAc,UAA0C,CAAC,MACvD,WAAW,YAAY,OAAO;AAClC;AAsCA,MAAa,UAAU,KAAK,WAAoB;AAMhD,MAAa,YAAY,KAAK,WAAsB;AAwBpD,MAAa,YAAY,KAAK,WAAsB;AAqFpD,IAAa,UAAb,cAA6B,QAAQ,QAA4B,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC;AAa1F,MAAa,WAAwB;CACnC,kBAAkB,OAAO,QAAQ,IAAI;CACrC,iBAAiB;CACjB,mBAAmB;AACrB;AACA,IAAa,SAAb,cAA4B,QAAQ,UAAuB,6BAA6B,EACtF,oBAAoB,SACtB,CAAC,CAAC,CAAC,CAAC;AAMJ,MAAM,UAAU,OAAO,OAAO;CAC5B,QAAQ,OAAO,SAAS,CAAC,SAAS,aAAa,CAAC;CAChD,cAAc,OAAO,OAAO,OAAO,MAAM;CACzC,aAAa,OAAO;CACpB,QAAQ,OAAO,OAAO,OAAO,MAAM;;CAEnC,cAAc,OAAO,OAAO,OAAO,MAAM;AAC3C,CAAC;AAED,MAAM,SAAS,OAAO,OAAO;CAC3B,MAAM,OAAO;CACb,SAAS,OAAO;CAChB,OAAO,OAAO;AAChB,CAAC;AAED,MAAM,WAAW,SAAiB,UAChCA,KAAY,IAAIC,aAAoB;CAAE;CAAS,GAAI,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM;AAAG,CAAC,CAAC;;AAG7F,MAAM,aAAa,SAAiB,KAAK,YAAY,CAAC,CAAC,QAAQ,OAAO,EAAE;AAExE,MAAa,OAIT,OAAO,IAAI,aAAa;CAC1B,MAAM,UAAU,OAAOC;CACvB,MAAM,UAAU,OAAOC;CACvB,MAAM,YAAY,OAAOC;CACzB,MAAM,WAAW,OAAOC;CAExB,MAAM,QAAQ,WACZ,OAAO,IAAI,aAAa;EACtB,MAAM,aAAa,OAAO,QAAQ,KAAK,OAAO,MAAM;EACpD,OAAO,IAAIC,WAAmB;GAC5B;GACA,WAAW,OAAO;GAClB,WAAW,OAAO,SAAS;EAC7B,CAAC;CACH,CAAC;;CAGH,MAAM,QAAQ,kBACZ,OAAO,IAAI,aAAa;EACtB,MAAM,aAAa,OAAO,UAAU,IAAI,cAAc,QAAQ;EAC9D,MAAM,MAAM,OAAO,QAAQ,eAAe,WAAW,cAAc,EAAE;EAGrE,OAAO;GAAE,QAAA,OAFa,QAAQ,KAAK,IAAI,UAAU;GAEhC,SAAA,OADMC,cAAuB,YAAY,cAAc,OAAO;EACtD;CAC3B,CAAC;CAEH,MAAM,gBAAgB,YAAiC,WACrD,WAAW,UACP,WAAW,KAAK,OAAO,UACvB,WAAW,gBACT,WAAW,KAAK,aAAa,UAC7B,KAAA;CAER,MAAM,cAAc,YAAiC,WACnD,WAAW,UACP,WAAW,KAAK,OAAO,SACvB,WAAW,gBACT,WAAW,KAAK,aAAa,SAC7B,KAAA;CAER,MAAM,YAAY,kBAChB,OAAO,IAAI,aAAa;EACtB,MAAM,aAAa,OAAO,UAAU,IAAI,cAAc,QAAQ;EAC9D,MAAM,SAAS,WAAW,YAAY,cAAc,MAAM;EAC1D,IAAI,WAAW,KAAA,GAAW;EAE1B,OAAO,OAAO,OADY,KAAK,aAAa,CACpB;CAC1B,CAAC;;CAGH,MAAM,UAAU,QAAQ,eAAe,mBAAmB,QAAQ,CAAC,CAAC,KAAK,OAAO,MAAM;CAEtF,MAAM,WAAiC,iBACrC,OAAO,IAAI,aAAa;EACtB,MAAM,SAAS,OAAO;EACtB,MAAM,aAAa,OAAO,QAAQ,YAAY,IAAI,YAAY;EAC9D,MAAM,gBAAgB,OAAO,QAAQ,eAAe,IAAI,WAAW,eAAe;EAClF,MAAM,aAAa,OAAO,UAAU,IAAI,cAAc,QAAQ;EAC9D,MAAM,YAAY,aAAa,YAAY,cAAc,MAAM;EAC/D,MAAM,SAAS,KAAyB,QACtC,IAAI,cAAc,QAClB,SAAS,cAAc,IAAI,SAAS,IAAI,SAAS,cAAc,GAAG,KAChE,OAAO;EACX,MAAM,WAAW,KAAyB,QACxC,IAAI,cAAc,QAClB,SAAS,cAAc,IAAI,SAAS,KAAK,SAAS,cAAc,GAAG;EACrE,MAAM,UAAU,OAAO,QAAQ,eAAe,WAAW,cAAc,EAAE;EACzE,MAAM,MAAM,OAAO,SAAS;EAC5B,IAAI,CAAC,MAAM,SAAS,GAAG,GAAG,OAAO;EACjC,IAAI,cAAc,KAAA,GAChB,OAAO,QAAQ,SAAS,GAAG,IAAK,cAAyB;EAC3D,OAAO,OAAO,QACX,SACC,WAAW,cAAc,MACzB,OAAO,IAAI,aAAa;GAGtB,MAAM,OAAO,OAAO,QAAQ,eAAe,IAAI,cAAc,EAAE,CAAC,CAAC,KAC/D,OAAO,IAAI,OAAO,IAAI,GACtB,OAAO,SACJ,UAAU,MAAM,OAAO,SAAS,kBAC3B,OAAO,QAAQ,OAAO,KAA4B,CAAC,CAC3D,CACF;GACA,IAAI,OAAO,OAAO,IAAI,KAAK,KAAK,MAAM,eAAe,UACnD,OAAO;GAET,MAAM,SAAS,OAAO,QAAQ,eAAe,WAAW,cAAc,EAAE;GACxE,IAAI,CAAC,MAAM,QAAQ,OAAO,SAAS,GAAG,GAAG,OAAO;GAChD,MAAM,SAAS,OAAO,QAAQ,KAAK,OAAO,UAAU;GACpD,MAAM,UAAU,OAAOA,cAAuB,YAAY,KAAK,MAAM,OAAO;GAC5E,MAAM,SAAS,OAAO,UAAU;IAAE;IAAQ;GAAQ,CAAC;GAGnD,OAAO,QAAQ,eACZ,OAAO,cAAc,IAAI,OAAO,KAAK,MAAM,CAAC,CAAC,CAC7C,KACC,OAAO,UAAU,UACf,MAAM,OAAO,SAAS,cACjB,WAAW,YAAY,cAAc,MAAM,CAAC,GAAG,MAAM,KAAK,OAAO,KAAA,CAAM,KACtE,OAAO,MACT,IACA,OAAO,IACb,CACF;GACF,OAAO;EACT,CAAC,CACH,CAAC,CACA,KACC,OAAO,SACJ,UAAU,MAAM,OAAO,SAAS,UAAU,CAAC,QAAQ,SAAS,GAAG,SAC1D,OAAO,QAAQ,SAAkB,CACzC,GACA,OAAO,SACJ,UAAU,MAAM,OAAO,SAAS,yBAC3B,OAAO,QAAQ,WAAoB,CAC3C,CACF;CACJ,CAAC;CAEH,MAAM,cAAc,eAClB,OAAO,IAAI,aAAa;EACtB,MAAM,gBAAgB,OAAO,QAAQ,eAAe,IAAI,WAAW,eAAe;EAClF,MAAM,YAAYP,KAChB,IAAIQ,UAAiB;GACnB,UAAU,cAAc;GACxB,cAAc,WAAW;EAC3B,CAAC,CACH;EACA,IAAI,cAAc,eAAe,UAAU,OAAO,OAAO;EACzD,KAAK,OAAO,QAAQ,WAAW,EAAE,OAAO,aAAa,OAAO,OAAO;EAEnE,QAAO,OADmB,UAAU,IAAI,cAAc,QAAQ,EAAA,CAC5C,QAAQ,OAAO,KAAK,aAAa,CAAC;CACtD,CAAC;CAEH,MAAM,eAAe,UAMnB,OAAO,IAAI,aAAa;EACtB,MAAM,YAAY,OAAOC;EACzB,MAAM,UAAU,MAAM,WAAW,QAAQ,MAAM,MAAM;EACrD,MAAM,OAAO,OAAO,QAAQ,aAAa;EACzC,MAAM,UAAU,MAAM,qBAAqB,QAAQ,eAAe,CAAC,KAAK,SAAS,UAAU,CAAC;EAC5F,IAAI,QAAQ,SAAS,GACnB,OAAO,OAAOT,KACZ,IAAIU,UAAiB,EACnB,SAAS,GAAG,MAAM,WAAW,KAAK,oBAAoB,QAAQ,KAAK,IAAI,IACzE,CAAC,CACH;EAEF,MAAM,gBAAgB,IAAIC,cAAsB;GAC9C,IAAI,OAAO,MAAM,MAAM;GACvB,SAAS,UAAU;GACnB,UAAU,MAAM,WAAW;GAC3B,QAAQ,MAAM;GACd,cAAc;GACd,SAAS,OAAOC,cAAuB,MAAM,YAAY,MAAM,OAAO,OAAO;GAC7E,OAAO,MAAM,OAAO,SAAS;GAC7B,YAAY;GACZ,WAAW,UAAU;GACrB,WAAW,OAAO,SAAS;EAC7B,CAAC;EACD,OAAO,QAAQ,eAAe,OAAO;GACnC;GACA,YAAY,OAAO,KAAK,MAAM,MAAM;EACtC,CAAC;EAED,OAAO;GAAE,YAAA,OADiB,QAAQ,YAAY,OAAO,cAAc,EAAE;GAChD;EAAQ;CAC/B,CAAC;;;;;CAMH,MAAM,eAAe,UAOnB,OAAO,IAAI,aAAa;EACtB,MAAM,YAAY,OAAOH;EAEzB,MAAM,OAAO,OADG,MAAM,WAAW,QAAQ,MAAM,MACrB,CAAC,CAAC,aAAa;EACzC,MAAM,UAAU,MAAM,qBAAqB,QAAQ,eAAe,CAAC,KAAK,SAAS,UAAU,CAAC;EAC5F,IAAI,QAAQ,SAAS,GACnB,OAAO,OAAOT,KACZ,IAAIU,UAAiB,EACnB,SAAS,GAAG,MAAM,WAAW,KAAK,oBAAoB,QAAQ,KAAK,IAAI,IACzE,CAAC,CACH;EAEF,MAAM,gBAAgB,IAAIC,cAAsB;GAC9C,IAAI,MAAM;GACV,SAAS,UAAU;GACnB,UAAU,MAAM,WAAW;GAC3B,QAAQ,MAAM;GACd,cAAc;GACd,SAAS,OAAOC,cAAuB,MAAM,YAAY,MAAM,OAAO,OAAO;GAC7E,OAAO,MAAM,OAAO,SAAS;GAC7B,YAAY;GACZ,WAAW,UAAU;GACrB,WAAW,OAAO,SAAS;EAC7B,CAAC;EACD,OAAO,QAAQ,eAAe,OAAO;GACnC;GACA,YAAY,OAAO,KAAK,MAAM,MAAM;GACpC,YAAY,MAAM;EACpB,CAAC;CACH,CAAC;CAEH,MAAM,cAAc,UAOlB,OAAO,IAAI,aAAa;EACtB,MAAM,SAAS,OAAOC,OAAkB,MAAM,MAAM;EACpD,MAAM,WAAW,OAAO,QAAQ,YAAY,SAAS,MAAM;EAC3D,IAAI,OAAO,OAAO,QAAQ,GAAG;GAC3B,IAAI,SAAS,MAAM,iBAAiB,MAAM,WAAW,IAAI,OAAO,SAAS;GACzE,OAAO,OAAO,QAAQ,GAAG,OAAO,6CAA6C,QAAQ;EACvF;EACA,MAAM,aACJ,MAAM,WAAW,KAAA,IACb,OAAO,MAAM,QAAQ,cAAc,MAAM,IAAA,WACrB,SAAS,EAAE,QAAQ,MAAM,OAAO,CAAC;EAC3D,QAAQ,WAAW,MAAnB;GACE,KAAK,qBACH,OAAO,UAAU,kBAAkB,EAAE,YAAY,WAAW,WAAW,CAAC;GAC1E,KAAK,YACH,OAAO,OAAOb,KAAY,IAAIc,SAAgB;IAAE,QAAQ;IAAQ,IAAI;GAAO,CAAC,CAAC;GAC/E,KAAK,YACH,OAAO,OAAO,QAAQ,YAAY,OAAO;IACvC,cAAc,MAAM,WAAW;IAC/B;IACA,MAAM,WAAW,OAAO;IACxB,OAAO,WAAW,OAAO;IACzB,QAAQ;KACN,GAAG,WAAW;KACd,SAAS,OAAOF,cAAuB,MAAM,YAAY,WAAW,OAAO,OAAO;IACpF;GACF,CAAC;EACL;CACF,CAAC;CAEH,MAAM,WAAW,YAAgC,aAC/C,aAAa,OACT,QAAQ,UAAU;EAAE;EAAY,YAAY;CAAK,CAAC,IAClD,oBAAoBG,aAClB,QAAQ,UAAU;EAAE;EAAY,YAAY;CAAS,CAAC,IACtD,QAAQ,kBAAkB;EAAE;EAAY,YAAY,SAAS;CAAW,CAAC;;CAGjF,MAAM,iBAAiB,eACrB,OAAO,IAAI,aAAa;EACtB,KAAK,MAAM,QAAQ,YAAY;GAE7B,MAAM,SAAQ,OADU,SAAS,QAAQ,MAAM,IAAI,EAAA,CAC5B,SAAS,YAC9B,QAAQ,SAAS,aACb,QAAQ,OAAO,QAAQ,SAAS,WAC9B,OAAO,SAAS,OAAO,CAAC,OAAO,UAAU,IAAI,CAAC,CAChD,IACA,CAAC,CACP;GACA,IAAI,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK;EACxD;EACA,OAAO,CAAC;CACV,CAAC;CAEH,MAAM,YAAmC,UACvC,OAAO,IAAI,aAAa;EACtB,MAAM,SAAS,OAAOF,OAAkB,KAAK;EAC7C,MAAMG,eAAaC,WAAsB,MAAM;EAC/C,MAAM,cAAc,OAAO,cAAcD,YAAU;EACnD,MAAM,YAGD,CAAC;EACN,KAAK,MAAM,cAAc,OAAO,QAAQ,YAAY,KAAK,GAAG;GAC1D,MAAM,UAAU,OAAO,WAAW,UAAU,CAAC,CAAC,KAC5C,OAAO,SAAS,YAAY,QAAQ,YAAY,CAAC,GACjD,OAAO,YAAY,OAAO,QAAQ,CAAC,CAAmC,CAAC,CACzE;GACA,KAAK,MAAM,UAAU,SACnB,IAAIA,aAAW,SAAS,OAAO,IAAwB,GACrD,UAAU,KAAK;IAAE,cAAc,WAAW;IAAI;GAAO,CAAC;EAG5D;EACA,KAAK,MAAM,QAAQA,cAAY;GAC7B,MAAM,UAAU,UACb,QAAQ,EAAE,aAAa,OAAO,SAAS,IAAI,CAAC,CAC5C,MAAM,MAAM,UAAU,KAAK,aAAa,cAAc,MAAM,YAAY,CAAC;GAC5E,IAAI,QAAQ,WAAW,GAAG;GAC1B,MAAM,CAAC,QAAQ;GACf,IAAI,QAAQ,WAAW,KAAK,SAAS,KAAA,GAAW,OAAO,UAAU,SAAS,IAAI;GAC9E,MAAM,WAAW,QAAQ,QAAQ,EAAE,aAAa;IAC9C,MAAM,aAAa,IAAI,KAAK,OAAO,eAAe,CAAC,EAAA,CAAG,IAAI,SAAS,CAAC;IACpE,OAAO,YAAY,SAAS,KAAK,YAAY,OAAO,SAAS,WAAW,IAAI,IAAI,CAAC;GACnF,CAAC;GACD,MAAM,CAAC,UAAU;GACjB,IAAI,SAAS,WAAW,KAAK,WAAW,KAAA,GAAW,OAAO,UAAU,SAAS,MAAM;GACnF,OAAO,UAAU,kBAAkB,EAAE,YAAY,QAAQ,CAAC;EAC5D;EACA,OAAO,UAAU,SAAS;GAAE;GAAa,MAAM,OAAO,WAAW;EAAE,CAAC;CACtE,CAAC;;CAGH,MAAM,UAAU,gBAA6E;EAC3F,IAAI,YAAY,WAAW,GAAG,OAAO;EACrC,MAAM,QAAQ,YAAY,IAAI,SAAS;EACvC,MAAM,QAAQ,UAAU,KAAK,CAAC,CAAC,QAAQ,eAAe;GACpD,MAAM,YAAY,WAAW,eAAe,CAAC,EAAA,CAAG,IAAI,SAAS;GAC7D,OACE,SAAS,SAAS,KAClB,MAAM,OAAO,SACX,SAAS,MAAM,WAAW,SAAS,UAAU,KAAK,SAAS,IAAI,QAAQ,CAAC,CAC1E;EAEJ,CAAC;EACD,MAAM,CAAC,QAAQ;EACf,OAAO,MAAM,WAAW,KAAK,SAAS,KAAA,IAAY,EAAE,UAAU,KAAK,GAAG,IAAI;CAC5E;CAEA,MAAM,SAA6B,UAAU,CAAC,MAC5C,OAAO,IAAI,aAAa;EACtB,MAAM,YAAyB,CAAC;EAChC,MAAM,cAAqC,CAAC;EAC5C,MAAM,SAAS,QAAQ,aAAa,KAAA,IAAY,KAAA,IAAY,EAAE,UAAU,QAAQ,SAAS;EACzF,KAAK,MAAM,cAAc,OAAO,QAAQ,YAAY,KAAK,MAAM,GAAG;GAChE,MAAM,gBAAgB,OAAO,QAAQ,eAAe,IAAI,WAAW,eAAe;GAKlF,MAAM,UAAU,OAAO,WAAW,UAAU,CAAC,CAAC,KAC5C,OAAO,SAAS,YAAY,QAAQ,YAAY,CAAC,GACjD,OAAO,IAAI,OAAO,IAAI,GACtB,OAAO,SACJ,UACC,MAAM,OAAO,SAAS,eACtB,MAAM,OAAO,SAAS,qBACtB,MAAM,OAAO,SAAS,mBAClB,OAAO,QAAQ,OAAO,KAAqC,CAAC,CACpE,CACF;GACA,YAAY,KAAK;IACf,cAAc,WAAW;IACzB,UAAU,cAAc;IACxB,OAAO,cAAc;IACrB,QAAQ,OAAO,OAAO,OAAO,IAAI,cAAc;GACjD,CAAC;GACD,IAAI,OAAO,OAAO,OAAO,GAAG;GAC5B,KAAK,MAAM,UAAU,QAAQ,OAC3B,UAAU,KAAK;IACb,cAAc,WAAW;IACzB,UAAU,cAAc;IACxB;GACF,CAAC;EAEL;EACA,OAAO;GACL,OAAO,UAAU,MACd,MAAM,UACL,KAAK,OAAO,KAAK,cAAc,MAAM,OAAO,IAAI,KAChD,KAAK,aAAa,cAAc,MAAM,YAAY,CACtD;GACA;GACA,WAAW,UAAU,KAAK,CAAC,CAAC,KAAK,gBAAgB;IAC/C,IAAI,WAAW;IACf,MAAM,WAAW;IACjB,SAASE,gBAAyB,UAAU;GAC9C,EAAE;EACJ;CACF,CAAC;CAEH,MAAM,WAAiC,UACrC,OAAO,IAAI,aAAa;EACtB,MAAM,SAAS,OAAOL,OAAkB,KAAK;EAC7C,MAAM,SAAS,OAAO;EACtB,MAAM,aAAa,OAAO,UAAU,OAAO,QAAQ,YAAY,SAAS,MAAM,CAAC;EAC/E,MAAM,aACJ,eAAe,OAAO,OAAO,OAAO,QAAQ,YAAY,IAAI,WAAW,YAAY;EACrF,MAAM,gBACJ,eAAe,OAAO,OAAO,OAAO,QAAQ,eAAe,IAAI,WAAW,eAAe;EAC3F,MAAM,SACJ,eAAe,OACX,OAAO,KAAK,IACZ,OAAO,QAAQ,SAAS,OAAO,WAAW,IAAI,cAAc;EAElE,MAAM,WAAW,eAAe,OAAO,CAAC,IAAI,OAAO,QAAQ,YAAY,KAAK,WAAW,EAAE;EACzF,MAAM,WAAgD,CAAC;EACvD,KAAK,MAAM,aAAa,OAAO,QAAQ,YAAY,KAAK,GAAG;GACzD,IAAI,UAAU,OAAO,YAAY,IAAI;GACrC,IAAI,EAAE,OAAO,OAAO,WAAW;IAAE,YAAY;IAAW;GAAO,CAAC,IAAI;GACpE,MAAM,yBAAyB,OAAO,QAAQ,eAAe,IAAI,UAAU,eAAe;GAC1F,IAAI,uBAAuB,eAAe,UAAU;GACpD,SAAS,KAAK;IACZ,YAAY;IACZ,UAAU,uBAAuB;IACjC,QAAQ,uBAAuB;GACjC,CAAC;EACH;EACA,OAAO;GACL;GACA;GACA;GACA,eACE,kBAAkB,OACd,OACA;IACE,IAAI,cAAc;IAClB,UAAU,cAAc;IACxB,QAAQ,cAAc;IACtB,cAAc,cAAc;IAC5B,YAAY,cAAc;IAC1B,OAAO,cAAc;GACvB;GACN,eAAe,OAAO,OAAO,MAAM,IAAK,OAAO,MAAM,SAAS,MAAM,OAAQ;GAC5E,mBAAmB,SAAS;GAC5B;GACA,WAAW,UAAU,KAAK,CAAC,CAAC,KAAK,gBAAgB;IAC/C,IAAI,WAAW;IACf,MAAM,WAAW;IACjB,SAASK,gBAAyB,UAAU;GAC9C,EAAE;EACJ;CACF,CAAC;CAEH,MAAM,SAA6B,UACjC,OAAO,IAAI,aAAa;EACtB,OAAO;EACP,MAAM,aAAa,OAAO,UAAU,IAAI,MAAM,QAAQ;EACtD,IAAI,MAAM,WAAW,KAAA,GAAW,OAAOL,OAAkB,MAAM,MAAM;EACrE,QAAQ,MAAM,OAAO,MAArB;GACE,KAAK,SAAS;IACZ,MAAM,QAAQ,WAAW,KAAK;IAC9B,IAAI,UAAU,KAAA,GACZ,OAAO,OAAO,QAAQ,GAAG,WAAW,KAAK,0BAA0B,QAAQ;IAE7E,MAAM,SAAS,OAAO,MAAM,aAAa,MAAM,OAAO,MAAM;IAC5D,MAAM,EAAE,YAAY,YAAY,OAAO,YAAY;KACjD;KACA,QAAQ;KACR;KACA,sBAAsB,MAAM;IAC9B,CAAC;IACD,MAAM,WACJ,MAAM,WAAW,KAAA,IACb,OACA,OAAO,WAAW;KAAE;KAAY;KAAY;KAAS,QAAQ,MAAM;IAAO,CAAC;IACjF,OAAO,QAAQ,YAAY,QAAQ;GACrC;GACA,KAAK;GACL,KAAK,eACH,OAAO,OAAO,YAAY;IACxB;IACA,QAAQ,MAAM;IACd,aAAa,MAAM;IACnB,QAAQ,MAAM,UAAU;IACxB,cAAc;GAChB,CAAC;EACL;CACF,CAAC;;CAGH,MAAM,eAAe,UAOnB,OAAO,IAAI,aAAa;EACtB,MAAM,YAAY,OAAOJ;EACzB,MAAM,SAAS,OAAO;EACtB,MAAM,cACJ,MAAM,OAAO,SAAS,UAClB,MAAM,WAAW,KAAK,QACtB,MAAM,WAAW,KAAK;EAC5B,IAAI,gBAAgB,KAAA,GAClB,OAAO,OAAO,QACZ,GAAG,MAAM,WAAW,KAAK,kBAAkB,MAAM,OAAO,KAAK,YAAY,EAAE,eAC3E,QACF;EAEF,IAAI,MAAM,gBAAgB,KAAA,GACxB,OAAO,OAAO,QAAQ,8CAA8C,aAAa;EAEnF,MAAM,cAAc,MAAM;EAC1B,MAAM,iBAAiB,OAAO,MAAM,MAAM;EAC1C,MAAMU,SAAO,MAAM,OAAO,SAAS,UAAU,OAAOC,KAAW,IAAI;EACnE,MAAM,WACJ,MAAM,OAAO,SAAS,UAClB,OAAQ,YAAmC,MAAM;GAC/C,OAAO;GACP;GACA,eAAeD,QAAM,iBAAiB;EACxC,CAAC,IACD,OAAQ,YAAyC,MAAM;GACrD,OAAO;GACP;EACF,CAAC;EACP,OAAO,QAAQ,cAAc,IAC3B,IAAIE,aAAqB;GACvB,IAAI;GACJ,SAAS,UAAU;GACnB,SAAS,UAAU;GACnB,UAAU,MAAM,WAAW;GAC3B,SAAS,OAAO,WAAW,OAAO,CAAC,CAAC;IAClC,QAAQ,MAAM,OAAO,SAAS,UAAU,UAAU;IAClD,cAAcF,QAAM,gBAAgB;IACpC;IACA,QAAQ,MAAM;IACd,cAAc,MAAM;GACtB,CAAC;GACD,UAAU,MAAM,OAAO,YAAY;GACnC,WAAW,SAAS,YAClB,OAAO,SAAS,KAChB,SAAS,OAAO,OAAO,iBAAiB,CAC1C;EACF,CAAC,CACH;EACA,OAAO,QAAQ,SAAS;GAAE,kBAAkB,SAAS;GAAkB;EAAe,CAAC;CACzF,CAAC;CAEH,MAAM,aAAqC,UACzC,OAAO,IAAI,aAAa;EACtB,OAAO;EACP,MAAM,aAAa,OAAO,QAAQ,YAAY,IAAI,MAAM,YAAY;EACpE,MAAM,WAAW,OAAO,QAAQ,eAAe,IAAI,WAAW,eAAe;EAC7E,MAAM,aAAa,OAAO,UAAU,IAAI,SAAS,QAAQ;EACzD,QAAQ,MAAM,OAAO,MAArB;GACE,KAAK,SAAS;IACZ,MAAM,QAAQ,WAAW,KAAK;IAC9B,IAAI,UAAU,KAAA,GACZ,OAAO,OAAO,QAAQ,GAAG,WAAW,KAAK,0BAA0B,QAAQ;IAE7E,MAAM,SAAS,OAAO,MAAM,aAAa,MAAM,OAAO,MAAM;IAC5D,OAAO,YAAY;KACjB;KACA,iBAAiB,SAAS;KAC1B,QAAQ;KACR;KACA,sBAAsB,MAAM;IAC9B,CAAC;IACD,OAAO,QAAQ,UAAU;KAAE;KAAY,YAAY;IAAK,CAAC;GAC3D;GACA,KAAK;GACL,KAAK,eACH,OAAO,OAAO,YAAY;IACxB;IACA,QAAQ,MAAM;IACd,aAAa,MAAM;IACnB,QAAQ;IACR,cAAc,WAAW;GAC3B,CAAC;EACL;CACF,CAAC;CAEH,MAAM,YAAmC,UACvC,OAAO,IAAI,aAAa;EAMtB,MAAM,WAAW,OAAO,OAAO,IAAI;GACjC,WAAW,IAAI,IAAI,MAAM,WAAW;GACpC,aACE,IAAIG,eAAsB,EACxB,QAAQ,IAAIrB,aAAoB;IAC9B,SAAS;IACT,OAAO;GACT,CAAC,EACH,CAAC;EACL,CAAC;EACD,MAAM,SAAS,OAAO,YAAY,SAAS,YAAY;EACvD,MAAM,mBAAmB,YACvBD,KAAY,IAAIuB,gBAAuB,EAAE,QAAQ,CAAC,CAAC;EACrD,IAAI,OAAO,UAAU,KAAA,GACnB,OAAO,OAAO,gBAAgB,qBAAqB,OAAO,OAAO;EAEnE,IAAI,OAAO,UAAU,MAAM,gBACzB,OAAO,OAAO,gBAAgB,gDAAgD;EAEhF,MAAM,OAAO,OAAO;EACpB,IAAI,SAAS,KAAA,GAAW,OAAO,OAAO,gBAAgB,sBAAsB;EAC5E,OAAO,OAAO,QAAQ,SACpB,gBAAgB,MAAM,kBACtB,OAAO,IAAI,aAAa;GACtB,MAAM,eAAe,OAAO,QAAQ,cAAc,IAAI,MAAM,cAAc;GAC1E,MAAM,UAAU,OAAOC,SAAc,SAAS,aAAa,SAAS,cAAc;GAClF,MAAM,aAAa,OAAO,UAAU,IAAI,aAAa,QAAQ;GAC7D,MAAM,SACJ,QAAQ,WAAW,UACf,OAAO,OAAO,IAAI,aAAa;IAC7B,MAAM,QAAQ,WAAW,KAAK;IAC9B,IAAI,UAAU,KAAA,KAAa,QAAQ,iBAAiB,MAClD,OAAO,OAAO,QACZ,GAAG,WAAW,KAAK,oCACnB,QACF;IAEF,OAAO,OAAO,MAAM,SAAS;KAC3B;KACA,aAAa,QAAQ;KACrB,cAAc,QAAQ;KACtB;IACF,CAAC;GACH,CAAC,IACD,OAAO,OAAO,IAAI,aAAa;IAC7B,MAAM,cAAc,WAAW,KAAK;IACpC,IAAI,gBAAgB,KAAA,GAClB,OAAO,OAAO,QACZ,GAAG,WAAW,KAAK,0CACnB,QACF;IAEF,OAAO,OAAO,YAAY,SAAS;KACjC;KACA,aAAa,QAAQ;KACrB;IACF,CAAC;GACH,CAAC;GACP,IAAI,QAAQ,iBAAiB,MAAM;IACjC,MAAM,OAAO,OAAO,QAAQ,YAAY,IAAI,QAAQ,YAAY;IAChE,MAAM,WAAW,OAAO,QAAQ,eAAe,IAAI,KAAK,eAAe;IACvE,OAAO,YAAY;KACjB;KACA,iBAAiB,SAAS;KAC1B,QAAQ,QAAQ;KAChB;KACA,sBAAsB,CAAC;IACzB,CAAC;IACD,OAAO,QAAQ,cAAc,QAAQ,MAAM,cAAc,CAAC,CAAC,KAAK,OAAO,MAAM;IAC7E,OAAO,QAAQ,UAAU;KAAE,YAAY;KAAM,YAAY;IAAK,CAAC;GACjE;GACA,MAAM,EAAE,YAAY,YAAY,OAAO,YAAY;IACjD;IACA,QAAQ,QAAQ;IAChB;IACA,sBAAsB,CAAC;GACzB,CAAC;GACD,MAAM,WACJ,QAAQ,WAAW,OACf,OACA,OAAO,WAAW;IAAE;IAAY;IAAY;IAAS,QAAQ,QAAQ;GAAO,CAAC;GAKnF,OAAO,QAAQ,cAAc,QAAQ,MAAM,cAAc,CAAC,CAAC,KAAK,OAAO,MAAM;GAC7E,OAAO,QAAQ,YAAY,QAAQ;EACrC,CAAC,CACH;CACF,CAAC;CAEH,MAAM,UAA+B,UACnC,OAAO,IAAI,aAAa;EACtB,MAAM,aAAa,OAAO,QAAQ,YAAY,IAAI,MAAM,YAAY;EACpE,MAAM,gBAAgB,OAAO,QAAQ,eAAe,IAAI,WAAW,eAAe;EAClF,MAAM,aAAa,OAAO,UAAU,IAAI,cAAc,QAAQ;EAC9D,MAAM,UAAU,OAAO,WAAW,UAAU;EAC5C,OAAO,OAAO,WAAW;GACvB;GACA;GACA;GACA,QAAQ,MAAM;GACd,GAAI,MAAM,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ,MAAM,OAAO;EAC/D,CAAC;CACH,CAAC;CAEH,MAAM,cAAuC,iBAC3C,OAAO,IAAI,aAAa;EACtB,OAAO;EACP,MAAM,aAAa,OAAO,QAAQ,YAAY,IAAI,YAAY;EAC9D,MAAM,cAAc,OAAO,QAAQ,YAAY,KAAK,YAAY;EAChE,OAAO,OAAO,QAAQ,cAAc,eAAe,QAAQ,YAAY,OAAO,WAAW,EAAE,CAAC;EAC5F,OAAO,QAAQ,YAAY,OAAO,YAAY;EAI9C,KAHmB,OAAO,QAAQ,YAAY,KAAK,EAAA,CAAG,MACnD,cAAc,UAAU,oBAAoB,WAAW,eAE9C,GAAG;EACf,MAAM,gBAAgB,OAAO,QAAQ,eAAe,IAAI,WAAW,eAAe;EAClF,OAAO,QAAQ,eAAe,OAAO,cAAc,IAAI,SAAS,aAAa,CAAC;CAChF,CAAC;CAEH,MAAM,WAAiC,iBACrC,OAAO,IAAI,aAAa;EACtB,MAAM,aAAa,OAAO,QAAQ,YAAY,IAAI,YAAY;EAC9D,MAAM,aAAa,OAAO,QAAQ,YAAY,IAAI,WAAW,YAAY;EACzE,MAAM,gBAAgB,OAAO,QAAQ,eAAe,IAAI,WAAW,eAAe;EAClF,MAAM,aAAa,OAAO,UAAU,IAAI,cAAc,QAAQ;EAC9D,MAAM,SAAS,OAAOA,SAAc,QAAQ,WAAW,QAAQ,QAAQ;EACvE,MAAM,SAA0B;GAC9B,GAAG;GACH,SAAS,OAAOjB,cAAuB,YAAY,OAAO,OAAO;EACnE;EACA,MAAM,QAAQ,OAAO,WAAW,UAAU;EAI1C,IAAI,EAAC,OADoB,MAAM,YAAY,EAAA,CAC5B,MAAM,cAAc,UAAU,SAAS,OAAO,IAAI,GAC/D,OAAO,OAAOP,KAAY,IAAIc,SAAgB;GAAE,QAAQ;GAAQ,IAAI,OAAO;EAAK,CAAC,CAAC;EAEpF,OAAO;GAAE,SAAS;GAAO;EAAO;CAClC,CAAC;CAEH,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA,SAAS,iBAAiB,QAAQ,YAAY,OAAO,YAAY;EACjE;EACA;EACA;CACF;AACF,CAAC;AAED,MAAa,QAIT,MAAM,OAAO,OAAO,CAAC,CAAC,IAAI;AAE9B,MAAM,YAEF,UAED,GAAG,SACF,OAAO,QAAQ,UAAU,YAAY,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;AAE/D,MAAa,UAAU,UAAU,YAAY,QAAQ,OAAO;AAC5D,MAAa,QAAQ,UAAU,YAAY,QAAQ,KAAK;AACxD,MAAa,WAAW,UAAU,YAAY,QAAQ,QAAQ;AAC9D,MAAa,QAAQ,UAAU,YAAY,QAAQ,KAAK;AACxD,MAAa,YAAY,UAAU,YAAY,QAAQ,SAAS;AAChE,MAAa,WAAW,UAAU,YAAY,QAAQ,QAAQ;AAC9D,MAAa,SAAS,UAAU,YAAY,QAAQ,MAAM;AAC1D,MAAa,SAAS,UAAU,YAAY,QAAQ,MAAM;AAC1D,MAAa,aAAa,UAAU,YAAY,QAAQ,UAAU;AAClE,MAAa,UAAU,UAAU,YAAY,QAAQ,OAAO;AAC5D,MAAa,UAAU,UAAU,YAAY,QAAQ,OAAO"}
|
|
1
|
+
{"version":3,"file":"Connect.mjs","names":["Errors.fail","Reason.InvalidInput","Storage.Service","Custody.Service","Providers.Service","Resolver.Service","Storage.Credential","Provider.decodeContext","Reason.Reconnect","Principal.Service","Reason.Forbidden","Storage.Authorization","Provider.encodeContext","DomainName.decode","Reason.NotFound","Storage.Attachment","candidates","DomainName.candidates","Provider.describeMethods","pkce","OAuth.pkce","Storage.Continuation","Callback.refuse","Errors.DomainKitError","Reason.Unauthenticated","Errors.decode"],"sources":["../src/Connect.ts"],"sourcesContent":["/**\n * Connect a provider, attach domains to it, and keep the credential alive. Interactive methods\n * (OAuth, integration) return a redirect and complete on callback; token methods connect in one\n * call. Reuse of an existing connection for a new domain is `attach`.\n */\nimport { Context, Data, DateTime, Duration, Effect, Layer, Option, Redacted, Schema } from \"effect\";\n\nimport * as Custody from \"./Custody.ts\";\nimport * as Errors from \"./internal/error.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as DomainName from \"./DomainName.ts\";\nimport { fresh } from \"./internal/ids.ts\";\nimport * as OAuth from \"./internal/oauth.ts\";\nimport * as Callback from \"./internal/callback.ts\";\nimport * as Principal from \"./Principal.ts\";\nimport * as Provider from \"./Provider.ts\";\nimport * as Providers from \"./Providers.ts\";\nimport * as Resolver from \"./Resolver.ts\";\nimport * as Storage from \"./Storage.ts\";\n\ntype Fx<A> = Effect.Effect<A, Errors.DomainKitError, Principal.Service>;\n\nexport type Method = Data.TaggedEnum<{\n /** Values keyed by the provider's declared `auth.token.fields`. */\n Token: { readonly values: Provider.TokenValues };\n OAuth: { readonly returnTo?: string };\n Integration: { readonly returnTo?: string };\n}>;\nconst MethodEnum = Data.taggedEnum<Method>();\n/** `token` accepts a lone string (`{ token }`) or a record; every value is wrapped in `Redacted`. */\nexport const Method = {\n ...MethodEnum,\n token: (\n values: string | Readonly<Record<string, Redacted.Redacted<string> | string | undefined>>,\n ): Method =>\n MethodEnum.Token({\n values:\n typeof values === \"string\"\n ? { token: Redacted.make(values) }\n : Object.fromEntries(\n Object.entries(values).map(([name, value]) => [\n name,\n typeof value === \"string\" ? Redacted.make(value) : value,\n ]),\n ),\n }),\n oauth: (options: { readonly returnTo?: string } = {}): Method => MethodEnum.OAuth(options),\n integration: (options: { readonly returnTo?: string } = {}): Method =>\n MethodEnum.Integration(options),\n} as const;\n\n/** Everything the UI needs about a domain's provider state. */\nexport interface Snapshot {\n readonly domain: string;\n readonly attachment: Storage.Attachment | null;\n readonly connection: Storage.Connection | null;\n readonly authorization: Pick<\n Storage.Authorization,\n \"id\" | \"provider\" | \"method\" | \"capabilities\" | \"revocation\" | \"label\"\n > | null;\n readonly lastReceiptId: string | null;\n /** How many domains this domain's connection serves, including this one; `0` without one. */\n readonly connectionDomains: number;\n /** Other connections of this owner that could serve this domain. */\n readonly reusable: ReadonlyArray<{\n readonly connection: Storage.Connection;\n readonly provider: string;\n readonly method: Storage.AuthMethod;\n }>;\n readonly providers: ReadonlyArray<{\n readonly id: string;\n readonly name: string;\n readonly methods: ReadonlyArray<Provider.MethodDescriptor>;\n }>;\n}\n\nexport type Started = Data.TaggedEnum<{\n Connected: {\n readonly connection: Storage.Connection;\n readonly attachment: Storage.Attachment | null;\n };\n Redirect: { readonly authorizationUrl: string; readonly continuationId: string };\n SelectionRequired: {\n readonly connection: Storage.Connection;\n readonly candidates: ReadonlyArray<Provider.Target>;\n };\n}>;\nexport const Started = Data.taggedEnum<Started>();\n\n/** A verified completion result. Errors stay on the server; do not serialize them to a browser. */\nexport type Completion = Data.TaggedEnum<{\n Connected: { readonly started: Exclude<Started, { readonly _tag: \"Redirect\" }> };\n Cancelled: { readonly error: Errors.DomainKitError };\n Failed: { readonly error: Errors.DomainKitError; readonly recovery: \"restart\" | \"inspect\" };\n}>;\nexport const Completion = Data.taggedEnum<Completion>();\n\n/** `attach` either attached the domain or needs the caller to pick a zone. */\nexport type Selection = Data.TaggedEnum<{\n SelectionRequired: { readonly candidates: ReadonlyArray<Provider.Target> };\n}>;\nexport const Selection = Data.taggedEnum<Selection>();\n\nexport type Attached = Storage.Attachment | Selection;\n\nexport type Refreshed = \"current\" | \"refreshed\" | \"reconnect\";\n\n/** Which of the principal's connections serves a domain, from nameserver evidence and zone lists. */\nexport type Discovery = Data.TaggedEnum<{\n Resolved: { readonly connectionId: string; readonly target: Provider.Target };\n SelectionRequired: {\n readonly candidates: ReadonlyArray<{\n readonly connectionId: string;\n readonly target: Provider.Target;\n }>;\n };\n /**\n * No connection reaches the domain. `host` names the one registered provider whose declared\n * nameserver suffixes cover every authoritative nameserver; `null` when none or several do.\n */\n NotFound: {\n readonly nameservers: ReadonlyArray<string>;\n readonly host: { readonly provider: string } | null;\n };\n}>;\nexport const Discovery = Data.taggedEnum<Discovery>();\n\n/** One zone a connection reaches, with the connection that reaches it. */\nexport interface Zone {\n readonly connectionId: string;\n readonly provider: string;\n readonly target: Provider.Target;\n}\n\n/**\n * A connection's standing in a zone listing. `reconnect` is a credential the provider turned down:\n * the connection is still the owner's, and the other connections' zones came back regardless.\n */\nexport interface ZoneConnection {\n readonly connectionId: string;\n readonly provider: string;\n /** What the provider called the account at connect time; `null` when it named none. */\n readonly label: string | null;\n readonly status: \"connected\" | \"reconnect\";\n}\n\n/** Every zone the principal's connections reach, and how each connection is standing. */\nexport interface Zones {\n /** Ordered by zone, then by connection, so two listings of the same state read the same. */\n readonly zones: ReadonlyArray<Zone>;\n readonly connections: ReadonlyArray<ZoneConnection>;\n /** Every provider a customer could connect from here, so an offer needs no second call. */\n readonly providers: Snapshot[\"providers\"];\n}\n\nexport interface Interface {\n readonly inspect: (domain: string) => Fx<Snapshot>;\n /**\n * Every zone the principal's connections can serve, from `Provider.Session.listTargets`. A\n * connection whose credential the provider turned down is marked `reconnect` in `connections`\n * and contributes no zones; the rest still list.\n */\n readonly zones: (options?: { readonly provider?: string }) => Fx<Zones>;\n /**\n * Resolve the domain's authoritative nameservers, then match them against the zones each of\n * the principal's connections can reach: the closest zone wins, a decisive nameserver match\n * breaks ties, and anything else needs a selection.\n */\n readonly discover: (domain: string) => Fx<Discovery>;\n /** Connect a provider and, when `domain` is given, attach it. Token methods finish here. */\n readonly start: (input: {\n readonly provider: string;\n readonly method: Method;\n readonly domain?: string;\n readonly callbackUrl?: string;\n }) => Fx<Started>;\n /**\n * Replace the credential behind a connection the owner already holds, keeping the connection and\n * every domain attached to it. This is what a customer means by reconnecting: starting again\n * would mint a second account and leave the domains on the one the provider turned down. The\n * provider is the connection's own; a credential for a different account is the customer's\n * choice, and the zone check on every session catches a zone it can no longer reach.\n */\n readonly reconnect: (input: {\n readonly connectionId: string;\n readonly method: Method;\n readonly callbackUrl?: string;\n }) => Fx<Started>;\n /** Finish an interactive connection from the provider callback (the full callback URL). */\n readonly complete: (input: {\n readonly continuationId: string;\n readonly callbackUrl: string;\n }) => Fx<Started>;\n /**\n * Classify only after live owner/actor verification inside the completion lock. Optional\n * `provider` binds the flow to a mounted route. Invalid flows fail without an outcome.\n * Cancellation and failure leave the continuation retryable; persistence is not atomic.\n */\n readonly completeOutcome: (input: {\n readonly continuationId: string;\n readonly callbackUrl: string;\n readonly provider?: string;\n }) => Fx<Completion>;\n readonly attach: (input: {\n readonly connectionId: string;\n readonly domain: string;\n readonly target?: Provider.Target;\n }) => Fx<Attached>;\n /** Records stay in DNS; DomainKit forgets the domain. Use `Cleanup` first to remove records. */\n readonly detach: (attachmentId: string) => Fx<void>;\n /** Detach every domain and revoke the credential at the provider when the method supports it. */\n readonly disconnect: (connectionId: string) => Fx<void>;\n /** Refresh a near-expiry credential, single-flighted. Called automatically before provider use. */\n readonly refresh: (connectionId: string) => Fx<Refreshed>;\n /** Live provider session for an attachment; used by Provision, Cleanup, Verify. */\n readonly session: (\n attachmentId: string,\n ) => Fx<{ readonly session: Provider.Session; readonly target: Provider.Target }>;\n}\n\nexport class Service extends Context.Service<Service, Interface>()(\"@domainkit/Connect\") {}\n\n/** Which owner-scoped reuse is allowed. Default: any connection of the same owner may serve any of its domains. */\nexport interface PolicyShape {\n readonly allowReuse: (input: {\n readonly connection: Storage.Connection;\n readonly domain: string;\n }) => Effect.Effect<boolean, never, Principal.Service>;\n /** Refresh this long before expiry. Default 10 minutes. */\n readonly refreshBeforeMs: number;\n /** Continuation lifetime. Default 15 minutes. */\n readonly continuationTtlMs: number;\n}\nexport const defaults: PolicyShape = {\n allowReuse: () => Effect.succeed(true),\n refreshBeforeMs: 10 * 60_000,\n continuationTtlMs: 15 * 60_000,\n};\nexport class Policy extends Context.Reference<PolicyShape>(\"@domainkit/Connect/Policy\", {\n defaultValue: () => defaults,\n}) {}\n\n// ---------------------------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------------------------\n\nconst Payload = Schema.Struct({\n method: Schema.Literals([\"oauth\", \"integration\"]),\n codeVerifier: Schema.NullOr(Schema.String),\n callbackUrl: Schema.String,\n domain: Schema.NullOr(Schema.String),\n /** Set when the flow is re-crediting a connection rather than making one. */\n connectionId: Schema.NullOr(Schema.String),\n});\n\nconst Target = Schema.Struct({\n zone: Schema.String,\n context: Schema.Unknown,\n label: Schema.String,\n});\n\nconst invalid = (message: string, field?: string) =>\n Errors.fail(new Reason.InvalidInput({ message, ...(field === undefined ? {} : { field }) }));\n\n/** Nameserver hostnames compare case-insensitively and without a trailing dot. */\nconst normalize = (name: string) => name.toLowerCase().replace(/\\.$/, \"\");\n\nexport const make: Effect.Effect<\n Interface,\n never,\n Storage.Service | Custody.Service | Providers.Service | Resolver.Service\n> = Effect.gen(function* () {\n const storage = yield* Storage.Service;\n const custody = yield* Custody.Service;\n const providers = yield* Providers.Service;\n const resolver = yield* Resolver.Service;\n\n const seal = (issued: Provider.IssuedCredential) =>\n Effect.gen(function* () {\n const ciphertext = yield* custody.seal(issued.secret);\n return new Storage.Credential({\n ciphertext,\n expiresAt: issued.expiresAt,\n rotatedAt: yield* DateTime.now,\n });\n });\n\n /** Plaintext credential with the context decoded from its stored envelope. */\n const open = (authorization: Storage.Authorization): Fx<Provider.Credential> =>\n Effect.gen(function* () {\n const definition = yield* providers.get(authorization.provider);\n const row = yield* storage.authorizations.credential(authorization.id);\n const secret = yield* custody.open(row.ciphertext);\n const context = yield* Provider.decodeContext(definition, authorization.context);\n return { secret, context };\n });\n\n const refresherFor = (definition: Provider.Definition, method: Storage.AuthMethod) =>\n method === \"oauth\"\n ? definition.auth.oauth?.refresh\n : method === \"integration\"\n ? definition.auth.integration?.refresh\n : undefined;\n\n const revokerFor = (definition: Provider.Definition, method: Storage.AuthMethod) =>\n method === \"oauth\"\n ? definition.auth.oauth?.revoke\n : method === \"integration\"\n ? definition.auth.integration?.revoke\n : undefined;\n\n const revokeAt = (authorization: Storage.Authorization): Fx<void> =>\n Effect.gen(function* () {\n const definition = yield* providers.get(authorization.provider);\n const revoke = revokerFor(definition, authorization.method);\n if (revoke === undefined) return;\n const credential = yield* open(authorization);\n yield* revoke(credential);\n });\n\n /** Finish revocations a crash interrupted; best effort, never blocks the caller. */\n const recover = storage.authorizations.recoverRevocations(revokeAt).pipe(Effect.ignore);\n\n const refresh: Interface[\"refresh\"] = (connectionId) =>\n Effect.gen(function* () {\n const policy = yield* Policy;\n const connection = yield* storage.connections.get(connectionId);\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const definition = yield* providers.get(authorization.provider);\n const refresher = refresherFor(definition, authorization.method);\n const dueAt = (row: Storage.Credential, now: DateTime.Utc) =>\n row.expiresAt !== null &&\n DateTime.toEpochMillis(row.expiresAt) - DateTime.toEpochMillis(now) <=\n policy.refreshBeforeMs;\n const expired = (row: Storage.Credential, now: DateTime.Utc) =>\n row.expiresAt !== null &&\n DateTime.toEpochMillis(row.expiresAt) <= DateTime.toEpochMillis(now);\n const current = yield* storage.authorizations.credential(authorization.id);\n const now = yield* DateTime.now;\n if (!dueAt(current, now)) return \"current\" as const;\n if (refresher === undefined)\n return expired(current, now) ? (\"reconnect\" as const) : (\"current\" as const);\n return yield* storage\n .withLock(\n `refresh:${authorization.id}`,\n Effect.gen(function* () {\n // Disconnect's two-phase revocation does not take this lock: re-read the row here\n // so a revocation that started meanwhile skips the refresh instead of racing it.\n const live = yield* storage.authorizations.get(authorization.id).pipe(\n Effect.map(Option.some),\n Effect.catchIf(\n (error) => error.reason._tag === \"NotFound\",\n () => Effect.succeed(Option.none<Storage.Authorization>()),\n ),\n );\n if (Option.isNone(live) || live.value.revocation !== \"active\") {\n return \"reconnect\" as const;\n }\n const latest = yield* storage.authorizations.credential(authorization.id);\n if (!dueAt(latest, yield* DateTime.now)) return \"current\" as const;\n const secret = yield* custody.open(latest.ciphertext);\n const context = yield* Provider.decodeContext(definition, live.value.context);\n const issued = yield* refresher({ secret, context });\n // A revocation that completed while the provider was issuing leaves no row to\n // rotate; revoke the fresh credential so it does not stay live without a home.\n yield* storage.authorizations\n .rotate(authorization.id, yield* seal(issued))\n .pipe(\n Effect.tapError((error) =>\n error.reason._tag === \"NotFound\"\n ? (revokerFor(definition, authorization.method)?.(issued) ?? Effect.void).pipe(\n Effect.ignore,\n )\n : Effect.void,\n ),\n );\n return \"refreshed\" as const;\n }),\n )\n .pipe(\n Effect.catchIf(\n (error) => error.reason._tag === \"Busy\" && !expired(current, now),\n () => Effect.succeed(\"current\" as const),\n ),\n Effect.catchIf(\n (error) => error.reason._tag === \"Unauthenticated\",\n () => Effect.succeed(\"reconnect\" as const),\n ),\n );\n });\n\n const sessionFor = (connection: Storage.Connection): Fx<Provider.Session> =>\n Effect.gen(function* () {\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const reconnect = Errors.fail(\n new Reason.Reconnect({\n provider: authorization.provider,\n connectionId: connection.id,\n }),\n );\n if (authorization.revocation !== \"active\") return yield* reconnect;\n if ((yield* refresh(connection.id)) === \"reconnect\") return yield* reconnect;\n const definition = yield* providers.get(authorization.provider);\n return definition.session(yield* open(authorization));\n });\n\n const connectWith = (input: {\n readonly definition: Provider.Definition;\n readonly method: Storage.AuthMethod;\n readonly issued: Provider.IssuedCredential;\n readonly requiredCapabilities: ReadonlyArray<Storage.Capability>;\n }): Fx<{ readonly connection: Storage.Connection; readonly session: Provider.Session }> =>\n Effect.gen(function* () {\n const principal = yield* Principal.Service;\n const session = input.definition.session(input.issued);\n const held = yield* session.capabilities();\n const missing = input.requiredCapabilities.filter((capability) => !held.includes(capability));\n if (missing.length > 0) {\n return yield* Errors.fail(\n new Reason.Forbidden({\n message: `${input.definition.name} credential lacks ${missing.join(\", \")}`,\n }),\n );\n }\n const authorization = new Storage.Authorization({\n id: yield* fresh(\"auth\"),\n ownerId: principal.ownerId,\n provider: input.definition.id,\n method: input.method,\n capabilities: held,\n context: yield* Provider.encodeContext(input.definition, input.issued.context),\n label: input.issued.label ?? null,\n revocation: \"active\",\n createdBy: principal.actorId,\n createdAt: yield* DateTime.now,\n });\n yield* storage.authorizations.upsert({\n authorization,\n credential: yield* seal(input.issued),\n });\n const connection = yield* storage.connections.create(authorization.id);\n return { connection, session };\n });\n\n /**\n * Put a fresh credential behind an authorization the owner already has. The row keeps its id, so\n * every connection over it, and every domain on those connections, stays exactly where it is.\n */\n const replaceWith = (input: {\n readonly definition: Provider.Definition;\n readonly authorizationId: string;\n readonly method: Storage.AuthMethod;\n readonly issued: Provider.IssuedCredential;\n readonly requiredCapabilities: ReadonlyArray<Storage.Capability>;\n }): Fx<void> =>\n Effect.gen(function* () {\n const principal = yield* Principal.Service;\n const session = input.definition.session(input.issued);\n const held = yield* session.capabilities();\n const missing = input.requiredCapabilities.filter((capability) => !held.includes(capability));\n if (missing.length > 0) {\n return yield* Errors.fail(\n new Reason.Forbidden({\n message: `${input.definition.name} credential lacks ${missing.join(\", \")}`,\n }),\n );\n }\n const authorization = new Storage.Authorization({\n id: input.authorizationId,\n ownerId: principal.ownerId,\n provider: input.definition.id,\n method: input.method,\n capabilities: held,\n context: yield* Provider.encodeContext(input.definition, input.issued.context),\n label: input.issued.label ?? null,\n revocation: \"active\",\n createdBy: principal.actorId,\n createdAt: yield* DateTime.now,\n });\n yield* storage.authorizations.upsert({\n authorization,\n credential: yield* seal(input.issued),\n expectedId: input.authorizationId,\n });\n });\n\n const attachWith = (input: {\n readonly definition: Provider.Definition;\n readonly connection: Storage.Connection;\n readonly session: Provider.Session;\n readonly domain: string;\n readonly target?: Provider.Target;\n }): Fx<Attached> =>\n Effect.gen(function* () {\n const domain = yield* DomainName.decode(input.domain);\n const existing = yield* storage.attachments.byDomain(domain);\n if (Option.isSome(existing)) {\n if (existing.value.connectionId === input.connection.id) return existing.value;\n return yield* invalid(`${domain} is already attached to another connection`, \"domain\");\n }\n const resolution: Provider.Resolution =\n input.target === undefined\n ? yield* input.session.resolveTarget(domain)\n : Provider.Resolution.Resolved({ target: input.target });\n switch (resolution._tag) {\n case \"SelectionRequired\":\n return Selection.SelectionRequired({ candidates: resolution.candidates });\n case \"NotFound\":\n return yield* Errors.fail(new Reason.NotFound({ entity: \"zone\", id: domain }));\n case \"Resolved\":\n return yield* storage.attachments.create({\n connectionId: input.connection.id,\n domain,\n zone: resolution.target.zone,\n label: resolution.target.label,\n target: {\n ...resolution.target,\n context: yield* Provider.encodeContext(input.definition, resolution.target.context),\n },\n });\n }\n });\n\n const started = (\n connection: Storage.Connection,\n attached: Attached | null,\n ): Exclude<Started, { readonly _tag: \"Redirect\" }> =>\n attached === null\n ? Started.Connected({ connection, attachment: null })\n : attached instanceof Storage.Attachment\n ? Started.Connected({ connection, attachment: attached })\n : Started.SelectionRequired({ connection, candidates: attached.candidates });\n\n /** Authoritative nameservers for the closest zone that answers an NS query. */\n const authoritative = (candidates: ReadonlyArray<DomainName.Model>) =>\n Effect.gen(function* () {\n for (const zone of candidates) {\n const outcomes = yield* resolver.resolve(zone, \"NS\");\n const names = outcomes.flatMap((outcome) =>\n outcome._tag === \"Answered\"\n ? outcome.answer.records.flatMap((record) =>\n record._tag === \"NS\" ? [record.nameserver] : [],\n )\n : [],\n );\n if (names.length > 0) return [...new Set(names)].sort();\n }\n return [] as ReadonlyArray<string>;\n });\n\n const discover: Interface[\"discover\"] = (input) =>\n Effect.gen(function* () {\n const domain = yield* DomainName.decode(input);\n const candidates = DomainName.candidates(domain);\n const nameservers = yield* authoritative(candidates);\n const reachable: Array<{\n readonly connectionId: string;\n readonly target: Provider.Target;\n }> = [];\n for (const connection of yield* storage.connections.list()) {\n const targets = yield* sessionFor(connection).pipe(\n Effect.flatMap((session) => session.listTargets()),\n Effect.catch(() => Effect.succeed([] as ReadonlyArray<Provider.Target>)),\n );\n for (const target of targets) {\n if (candidates.includes(target.zone as DomainName.Model)) {\n reachable.push({ connectionId: connection.id, target });\n }\n }\n }\n for (const zone of candidates) {\n const matches = reachable\n .filter(({ target }) => target.zone === zone)\n .sort((left, right) => left.connectionId.localeCompare(right.connectionId));\n if (matches.length === 0) continue;\n const [only] = matches;\n if (matches.length === 1 && only !== undefined) return Discovery.Resolved(only);\n const decisive = matches.filter(({ target }) => {\n const configured = new Set((target.nameservers ?? []).map(normalize));\n return nameservers.length > 0 && nameservers.every((name) => configured.has(name));\n });\n const [winner] = decisive;\n if (decisive.length === 1 && winner !== undefined) return Discovery.Resolved(winner);\n return Discovery.SelectionRequired({ candidates: matches });\n }\n return Discovery.NotFound({ nameservers, host: hostOf(nameservers) });\n });\n\n /** The one registered provider whose declared suffixes cover every nameserver, else `null`. */\n const hostOf = (nameservers: ReadonlyArray<string>): { readonly provider: string } | null => {\n if (nameservers.length === 0) return null;\n const names = nameservers.map(normalize);\n const hosts = providers.list().filter((definition) => {\n const suffixes = (definition.nameservers ?? []).map(normalize);\n return (\n suffixes.length > 0 &&\n names.every((name) =>\n suffixes.some((suffix) => name === suffix || name.endsWith(`.${suffix}`)),\n )\n );\n });\n const [host] = hosts;\n return hosts.length === 1 && host !== undefined ? { provider: host.id } : null;\n };\n\n const zones: Interface[\"zones\"] = (options = {}) =>\n Effect.gen(function* () {\n const reachable: Array<Zone> = [];\n const connections: Array<ZoneConnection> = [];\n const filter = options.provider === undefined ? undefined : { provider: options.provider };\n for (const connection of yield* storage.connections.list(filter)) {\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n // A credential the provider turned down is one connection's problem, not the listing's:\n // it is reported as needing a reconnect and every other connection still answers. Any\n // other failure is the listing's own, so it is raised rather than dressed up as an\n // authorization the customer has to grant again.\n const targets = yield* sessionFor(connection).pipe(\n Effect.flatMap((session) => session.listTargets()),\n Effect.map(Option.some),\n Effect.catchIf(\n (error) =>\n error.reason._tag === \"Reconnect\" ||\n error.reason._tag === \"Unauthenticated\" ||\n error.reason._tag === \"Forbidden\",\n () => Effect.succeed(Option.none<ReadonlyArray<Provider.Target>>()),\n ),\n );\n connections.push({\n connectionId: connection.id,\n provider: authorization.provider,\n label: authorization.label,\n status: Option.isNone(targets) ? \"reconnect\" : \"connected\",\n });\n if (Option.isNone(targets)) continue;\n for (const target of targets.value) {\n reachable.push({\n connectionId: connection.id,\n provider: authorization.provider,\n target,\n });\n }\n }\n return {\n zones: reachable.sort(\n (left, right) =>\n left.target.zone.localeCompare(right.target.zone) ||\n left.connectionId.localeCompare(right.connectionId),\n ),\n connections,\n providers: providers.list().map((definition) => ({\n id: definition.id,\n name: definition.name,\n methods: Provider.describeMethods(definition),\n })),\n };\n });\n\n const inspect: Interface[\"inspect\"] = (input) =>\n Effect.gen(function* () {\n const domain = yield* DomainName.decode(input);\n const policy = yield* Policy;\n const attachment = Option.getOrNull(yield* storage.attachments.byDomain(domain));\n const connection =\n attachment === null ? null : yield* storage.connections.get(attachment.connectionId);\n const authorization =\n connection === null ? null : yield* storage.authorizations.get(connection.authorizationId);\n const latest =\n attachment === null\n ? Option.none()\n : yield* storage.attempts.latest(attachment.id, \"provisioning\");\n // Letting a connection go takes every domain on it, so the UI has to be able to say how many.\n const attached = connection === null ? [] : yield* storage.attachments.list(connection.id);\n const reusable: Array<Snapshot[\"reusable\"][number]> = [];\n for (const candidate of yield* storage.connections.list()) {\n if (candidate.id === connection?.id) continue;\n if (!(yield* policy.allowReuse({ connection: candidate, domain }))) continue;\n const candidateAuthorization = yield* storage.authorizations.get(candidate.authorizationId);\n if (candidateAuthorization.revocation !== \"active\") continue;\n reusable.push({\n connection: candidate,\n provider: candidateAuthorization.provider,\n method: candidateAuthorization.method,\n });\n }\n return {\n domain,\n attachment,\n connection,\n authorization:\n authorization === null\n ? null\n : {\n id: authorization.id,\n provider: authorization.provider,\n method: authorization.method,\n capabilities: authorization.capabilities,\n revocation: authorization.revocation,\n label: authorization.label,\n },\n lastReceiptId: Option.isSome(latest) ? (latest.value.receipt?.id ?? null) : null,\n connectionDomains: attached.length,\n reusable,\n providers: providers.list().map((definition) => ({\n id: definition.id,\n name: definition.name,\n methods: Provider.describeMethods(definition),\n })),\n };\n });\n\n const start: Interface[\"start\"] = (input) =>\n Effect.gen(function* () {\n yield* recover;\n const definition = yield* providers.get(input.provider);\n if (input.domain !== undefined) yield* DomainName.decode(input.domain);\n switch (input.method._tag) {\n case \"Token\": {\n const token = definition.auth.token;\n if (token === undefined) {\n return yield* invalid(`${definition.name} does not accept tokens`, \"method\");\n }\n const issued = yield* token.authenticate(input.method.values);\n const { connection, session } = yield* connectWith({\n definition,\n method: \"token\",\n issued,\n requiredCapabilities: token.requiredCapabilities,\n });\n const attached =\n input.domain === undefined\n ? null\n : yield* attachWith({ definition, connection, session, domain: input.domain });\n return started(connection, attached);\n }\n case \"OAuth\":\n case \"Integration\":\n return yield* redirectFor({\n definition,\n method: input.method,\n callbackUrl: input.callbackUrl,\n domain: input.domain ?? null,\n connectionId: null,\n });\n }\n });\n\n /** The redirect an interactive method answers with, and the continuation that finishes it. */\n const redirectFor = (input: {\n readonly definition: Provider.Definition;\n readonly method: Extract<Method, { readonly _tag: \"OAuth\" | \"Integration\" }>;\n readonly callbackUrl: string | undefined;\n readonly domain: string | null;\n readonly connectionId: string | null;\n }): Fx<Started> =>\n Effect.gen(function* () {\n const principal = yield* Principal.Service;\n const policy = yield* Policy;\n const interactive =\n input.method._tag === \"OAuth\"\n ? input.definition.auth.oauth\n : input.definition.auth.integration;\n if (interactive === undefined) {\n return yield* invalid(\n `${input.definition.name} does not offer ${input.method._tag.toLowerCase()} connections`,\n \"method\",\n );\n }\n if (input.callbackUrl === undefined) {\n return yield* invalid(\"Interactive connections need a callbackUrl\", \"callbackUrl\");\n }\n const callbackUrl = input.callbackUrl;\n const continuationId = yield* fresh(\"cont\");\n const pkce = input.method._tag === \"OAuth\" ? yield* OAuth.pkce() : null;\n const redirect =\n input.method._tag === \"OAuth\"\n ? yield* (interactive as Provider.OAuthAuth).start({\n state: continuationId,\n callbackUrl,\n codeChallenge: pkce?.codeChallenge ?? \"\",\n })\n : yield* (interactive as Provider.IntegrationAuth).start({\n state: continuationId,\n callbackUrl,\n });\n yield* storage.continuations.put(\n new Storage.Continuation({\n id: continuationId,\n ownerId: principal.ownerId,\n actorId: principal.actorId,\n provider: input.definition.id,\n payload: Schema.encodeSync(Payload)({\n method: input.method._tag === \"OAuth\" ? \"oauth\" : \"integration\",\n codeVerifier: pkce?.codeVerifier ?? null,\n callbackUrl,\n domain: input.domain,\n connectionId: input.connectionId,\n }),\n returnTo: input.method.returnTo ?? null,\n expiresAt: DateTime.addDuration(\n yield* DateTime.now,\n Duration.millis(policy.continuationTtlMs),\n ),\n }),\n );\n return Started.Redirect({ authorizationUrl: redirect.authorizationUrl, continuationId });\n });\n\n const reconnect: Interface[\"reconnect\"] = (input) =>\n Effect.gen(function* () {\n yield* recover;\n const connection = yield* storage.connections.get(input.connectionId);\n const existing = yield* storage.authorizations.get(connection.authorizationId);\n const definition = yield* providers.get(existing.provider);\n switch (input.method._tag) {\n case \"Token\": {\n const token = definition.auth.token;\n if (token === undefined) {\n return yield* invalid(`${definition.name} does not accept tokens`, \"method\");\n }\n const issued = yield* token.authenticate(input.method.values);\n yield* replaceWith({\n definition,\n authorizationId: existing.id,\n method: \"token\",\n issued,\n requiredCapabilities: token.requiredCapabilities,\n });\n return Started.Connected({ connection, attachment: null });\n }\n case \"OAuth\":\n case \"Integration\":\n return yield* redirectFor({\n definition,\n method: input.method,\n callbackUrl: input.callbackUrl,\n domain: null,\n connectionId: connection.id,\n });\n }\n });\n\n const completeOutcome: Interface[\"completeOutcome\"] = (input) =>\n storage.withLock(\n `continuation:${input.continuationId}`,\n Effect.gen(function* () {\n const continuation = yield* storage.continuations.get(input.continuationId);\n const principal = yield* Principal.Service;\n if (\n continuation.ownerId !== principal.ownerId ||\n continuation.actorId !== principal.actorId ||\n (input.provider !== undefined && continuation.provider !== input.provider)\n ) {\n return yield* Callback.refuse;\n }\n const callback = yield* Effect.try({\n try: () => new URL(input.callbackUrl),\n catch: () =>\n new Errors.DomainKitError({\n reason: new Reason.InvalidInput({\n message: \"callbackUrl is not a URL\",\n field: \"callbackUrl\",\n }),\n }),\n });\n const params = Object.fromEntries(callback.searchParams);\n const failed = (message: string) =>\n Completion.Failed({\n error: new Errors.DomainKitError({ reason: new Reason.Unauthenticated({ message }) }),\n recovery: \"restart\",\n });\n // State verification precedes denial classification, including on the direct Effect API.\n if (params.state !== input.continuationId)\n return failed(\"Callback state does not match the continuation\");\n if (params.error !== undefined) {\n const outcome = failed(`Provider returned ${params.error}`);\n // OAuth's standardized consent denial is the only cancellation code we recognize.\n return params.error === \"access_denied\"\n ? Completion.Cancelled({ error: outcome.error })\n : outcome;\n }\n const code = params.code;\n if (code === undefined) return failed(\"Callback has no code\");\n // Once persistence starts, even a failure may leave a credential or connection behind.\n let persisting = false;\n return yield* Effect.gen(function* () {\n const payload = yield* Errors.decode(Payload, continuation.payload, \"continuation\");\n const definition = yield* providers.get(continuation.provider);\n const issued =\n payload.method === \"oauth\"\n ? yield* Effect.gen(function* () {\n const oauth = definition.auth.oauth;\n if (oauth === undefined || payload.codeVerifier === null) {\n return yield* invalid(\n `${definition.name} does not offer oauth connections`,\n \"method\",\n );\n }\n return yield* oauth.complete({\n code,\n callbackUrl: payload.callbackUrl,\n codeVerifier: payload.codeVerifier,\n params,\n });\n })\n : yield* Effect.gen(function* () {\n const integration = definition.auth.integration;\n if (integration === undefined) {\n return yield* invalid(\n `${definition.name} does not offer integration connections`,\n \"method\",\n );\n }\n return yield* integration.complete({\n code,\n callbackUrl: payload.callbackUrl,\n params,\n });\n });\n persisting = true;\n if (payload.connectionId !== null) {\n const held = yield* storage.connections.get(payload.connectionId);\n const existing = yield* storage.authorizations.get(held.authorizationId);\n yield* replaceWith({\n definition,\n authorizationId: existing.id,\n method: payload.method,\n issued,\n requiredCapabilities: [],\n });\n yield* storage.continuations.consume(input.continuationId).pipe(Effect.ignore);\n return Started.Connected({ connection: held, attachment: null });\n }\n const { connection, session } = yield* connectWith({\n definition,\n method: payload.method,\n issued,\n requiredCapabilities: [],\n });\n const attached =\n payload.domain === null\n ? null\n : yield* attachWith({ definition, connection, session, domain: payload.domain });\n // The connection is durable at this point. Whatever happens to the continuation\n // now (expired, already spent, or a storage failure while spending it) no longer\n // changes the outcome: the row expires on its own and a replayed callback fails at\n // the provider, so the persisted result is returned.\n yield* storage.continuations.consume(input.continuationId).pipe(Effect.ignore);\n return started(connection, attached);\n }).pipe(\n Effect.map((result) => Completion.Connected({ started: result })),\n Effect.catch((error) =>\n Effect.succeed(\n Completion.Failed({\n error,\n recovery:\n !persisting && error.reason._tag === \"Unauthenticated\" ? \"restart\" : \"inspect\",\n }),\n ),\n ),\n );\n }),\n );\n\n const complete: Interface[\"complete\"] = (input) =>\n completeOutcome(input).pipe(\n Effect.flatMap((outcome) =>\n outcome._tag === \"Connected\" ? Effect.succeed(outcome.started) : Effect.fail(outcome.error),\n ),\n );\n\n const attach: Interface[\"attach\"] = (input) =>\n Effect.gen(function* () {\n const connection = yield* storage.connections.get(input.connectionId);\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const definition = yield* providers.get(authorization.provider);\n const session = yield* sessionFor(connection);\n return yield* attachWith({\n definition,\n connection,\n session,\n domain: input.domain,\n ...(input.target === undefined ? {} : { target: input.target }),\n });\n });\n\n const disconnect: Interface[\"disconnect\"] = (connectionId) =>\n Effect.gen(function* () {\n yield* recover;\n const connection = yield* storage.connections.get(connectionId);\n const attachments = yield* storage.attachments.list(connectionId);\n yield* Effect.forEach(attachments, (attachment) => storage.attachments.remove(attachment.id));\n yield* storage.connections.remove(connectionId);\n const remaining = (yield* storage.connections.list()).some(\n (candidate) => candidate.authorizationId === connection.authorizationId,\n );\n if (remaining) return;\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n yield* storage.authorizations.revoke(authorization.id, revokeAt(authorization));\n });\n\n const session: Interface[\"session\"] = (attachmentId) =>\n Effect.gen(function* () {\n const attachment = yield* storage.attachments.get(attachmentId);\n const connection = yield* storage.connections.get(attachment.connectionId);\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const definition = yield* providers.get(authorization.provider);\n const stored = yield* Errors.decode(Target, attachment.target, \"target\");\n const target: Provider.Target = {\n ...stored,\n context: yield* Provider.decodeContext(definition, stored.context),\n };\n const built = yield* sessionFor(connection);\n // The zone must still be reachable by this credential: it may have moved account, been\n // deleted, or become a kind the provider cannot host records in.\n const reachable = yield* built.listTargets();\n if (!reachable.some((candidate) => candidate.zone === target.zone)) {\n return yield* Errors.fail(new Reason.NotFound({ entity: \"zone\", id: target.zone }));\n }\n return { session: built, target };\n });\n\n return {\n inspect,\n zones,\n discover,\n start,\n reconnect,\n complete,\n completeOutcome,\n attach,\n detach: (attachmentId) => storage.attachments.remove(attachmentId),\n disconnect,\n refresh,\n session,\n };\n});\n\nexport const layer: Layer.Layer<\n Service,\n never,\n Storage.Service | Custody.Service | Providers.Service | Resolver.Service\n> = Layer.effect(Service)(make);\n\nconst accessor =\n <Args extends ReadonlyArray<unknown>, A>(\n pick: (service: Interface) => (...args: Args) => Fx<A>,\n ): ((...args: Args) => Effect.Effect<A, Errors.DomainKitError, Principal.Service | Service>) =>\n (...args) =>\n Effect.flatMap(Service, (service) => pick(service)(...args));\n\nexport const inspect = accessor((service) => service.inspect);\nexport const zones = accessor((service) => service.zones);\nexport const discover = accessor((service) => service.discover);\nexport const start = accessor((service) => service.start);\nexport const reconnect = accessor((service) => service.reconnect);\nexport const complete = accessor((service) => service.complete);\nexport const completeOutcome = accessor((service) => service.completeOutcome);\nexport const attach = accessor((service) => service.attach);\nexport const detach = accessor((service) => service.detach);\nexport const disconnect = accessor((service) => service.disconnect);\nexport const refresh = accessor((service) => service.refresh);\nexport const session = accessor((service) => service.session);\n"],"mappings":"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"}
|
package/dist/Provider.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Provider.mjs","names":["Errors.decode","Errors.DomainKitError","Reason.InvalidInput","DomainName.candidates","Errors.fail","Reason.Unsupported"],"sources":["../src/Provider.ts"],"sourcesContent":["/**\n * One declarative definition per DNS provider. Auth methods are optional cases, so a provider that\n * only issues personal tokens declares `token` and nothing else. The definition drives connection\n * routes, refresh, the UI method catalog, and rebuilding a session from stored context.\n */\nimport { Data, type DateTime, Effect, Redacted, Schema } from \"effect\";\n\nimport type * as DnsRecord from \"./DnsRecord.ts\";\nimport * as Errors from \"./internal/error.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as DomainName from \"./DomainName.ts\";\nimport type { Capability } from \"./Storage.ts\";\n\nexport type Fx<A> = Effect.Effect<A, Errors.DomainKitError>;\n\n/** A record as the provider reports it, with the id needed to read or delete it later. */\nexport interface ObservedRecord {\n readonly record: DnsRecord.Observed;\n readonly providerRecordId: string | null;\n}\n\n/** What a session can do inside one zone. The only thing planning and applying touch. */\nexport interface Dns {\n readonly list: (zone: string) => Fx<ReadonlyArray<ObservedRecord>>;\n /** Must return the provider's id for the new record; cleanup deletes by that id. */\n readonly create: (\n zone: string,\n record: DnsRecord.Model,\n ) => Fx<{ readonly providerRecordId: string }>;\n readonly get: (zone: string, providerRecordId: string) => Fx<DnsRecord.Observed | null>;\n readonly delete: (zone: string, providerRecordId: string) => Fx<void>;\n}\n\n/** A zone the credential can reach, with the provider identity needed to address it later. */\nexport interface Target {\n readonly zone: string;\n readonly context: unknown;\n readonly label: string;\n /** The zone's nameservers as the provider reports them; `Connect.discover` matches on them. */\n readonly nameservers?: ReadonlyArray<string>;\n}\n\nexport type Resolution = Data.TaggedEnum<{\n Resolved: { readonly target: Target };\n SelectionRequired: { readonly candidates: ReadonlyArray<Target> };\n NotFound: {};\n}>;\nexport const Resolution = Data.taggedEnum<Resolution>();\n\n/** A credential bound to provider account context. */\nexport interface Session {\n readonly listTargets: () => Fx<ReadonlyArray<Target>>;\n readonly resolveTarget: (domain: string) => Fx<Resolution>;\n readonly dns: (target: Target) => Dns;\n /** Capabilities actually held; promoted onto the authorization after first use. */\n readonly capabilities: () => Fx<ReadonlyArray<Capability>>;\n}\n\n/**\n * Secret material plus the account context needed to rebuild a session. `secret` is opaque to\n * DomainKit: a provider may pack an access token and a refresh token into it.\n */\nexport interface Credential {\n readonly secret: Redacted.Redacted<string>;\n readonly context: unknown;\n}\n\n/** A credential the provider just issued or refreshed; `null` expiry means it does not expire. */\nexport interface IssuedCredential extends Credential {\n readonly expiresAt: DateTime.Utc | null;\n /**\n * What the provider calls the account this credential reaches, so a UI names the connection\n * without a provider call. Omit it, or answer `null`, when the provider cannot name one: a\n * credential that spans several accounts has no single name.\n */\n readonly label?: string | null;\n}\n\n/** Raw token-method input keyed by the provider's declared field names. */\nexport type TokenValues = Readonly<Record<string, Redacted.Redacted<string> | undefined>>;\n\nexport interface TokenAuth {\n readonly label: string;\n /** Where the customer creates the token; shown in the UI. */\n readonly docsUrl?: string;\n readonly requiredCapabilities: ReadonlyArray<Capability>;\n /**\n * The values this method needs, declared once so forms render from it: secrets as\n * `Schema.RedactedFromValue(Schema.String)`, optional ones through `Schema.optionalKey`.\n */\n readonly fields: Schema.Struct<Schema.Struct.Fields>;\n /** Validate the values (already decoded with `fields`) and return account context. */\n readonly authenticate: (values: TokenValues) => Fx<IssuedCredential>;\n}\n\n/** Build a `TokenAuth` whose `authenticate` receives the record decoded by `fields`. */\n/** Field schemas whose decoding needs no services, so `authenticate` stays a plain `Fx`. */\nexport type TokenFieldSchemas = { readonly [name: string]: Schema.ConstraintDecoder<unknown> };\n\nexport const tokenAuth = <const Fields extends TokenFieldSchemas>(input: {\n readonly label: string;\n readonly docsUrl?: string;\n readonly requiredCapabilities: ReadonlyArray<Capability>;\n readonly fields: Schema.Struct<Fields>;\n readonly authenticate: (values: Schema.Struct<Fields>[\"Type\"]) => Fx<IssuedCredential>;\n}): TokenAuth => ({\n label: input.label,\n ...(input.docsUrl === undefined ? {} : { docsUrl: input.docsUrl }),\n requiredCapabilities: input.requiredCapabilities,\n fields: input.fields as Schema.Struct<Schema.Struct.Fields>,\n authenticate: (values) =>\n decodeTokenValues(input.fields, values).pipe(Effect.flatMap(input.authenticate)),\n});\n\nconst decodeTokenValues = <Fields extends TokenFieldSchemas>(\n fields: Schema.Struct<Fields>,\n values: TokenValues,\n): Effect.Effect<Schema.Struct<Fields>[\"Type\"], Errors.DomainKitError> => {\n const raw: Record<string, string> = {};\n for (const [name, value] of Object.entries(values)) {\n if (value !== undefined) raw[name] = Redacted.value(value);\n }\n return Errors.decode(fields, raw, \"method\");\n};\n\nexport interface FieldDescriptor {\n readonly name: string;\n readonly required: boolean;\n readonly secret: boolean;\n}\n\n/** What a UI needs to render one auth method; `fields` is `null` for interactive methods. */\nexport interface MethodDescriptor {\n readonly kind: AuthMethod;\n readonly label: string;\n readonly docsUrl: string | null;\n readonly fields: ReadonlyArray<FieldDescriptor> | null;\n}\n\n/** Field descriptors derived from `TokenAuth.fields`: optional keys are not required, `Redacted` values are secret. */\nexport const tokenFields = (auth: TokenAuth): ReadonlyArray<FieldDescriptor> =>\n Object.entries(auth.fields.fields).map(([name, field]) => {\n const ast = (field as { readonly ast: FieldAst }).ast;\n return {\n name,\n required: ast.context?.isOptional !== true,\n secret: ast._tag === \"Declaration\",\n };\n });\n\ninterface FieldAst {\n readonly _tag: string;\n readonly context?: { readonly isOptional?: boolean } | undefined;\n}\n\n/**\n * Every method a provider offers, in the order a UI should present them: the interactive ones a\n * customer clicks through first, then the token they have to go and fetch themselves.\n */\nexport const describeMethods = (definition: Definition): ReadonlyArray<MethodDescriptor> => {\n const found: Array<MethodDescriptor> = [];\n if (definition.auth.oauth !== undefined) {\n found.push({ kind: \"oauth\", label: definition.auth.oauth.label, docsUrl: null, fields: null });\n }\n if (definition.auth.integration !== undefined) {\n found.push({\n kind: \"integration\",\n label: definition.auth.integration.label,\n docsUrl: null,\n fields: null,\n });\n }\n if (definition.auth.token !== undefined) {\n found.push({\n kind: \"token\",\n label: definition.auth.token.label,\n docsUrl: definition.auth.token.docsUrl ?? null,\n fields: tokenFields(definition.auth.token),\n });\n }\n return found;\n};\n\nexport interface CallbackInput {\n readonly code: string;\n /** The redirect URI registered at start; token exchanges echo it. */\n readonly callbackUrl: string;\n /** Every query parameter the provider appended to the callback. */\n readonly params: Readonly<Record<string, string>>;\n}\n\nexport interface OAuthAuth {\n readonly label: string;\n readonly scopes: ReadonlyArray<string>;\n readonly start: (input: {\n readonly state: string;\n readonly callbackUrl: string;\n readonly codeChallenge: string;\n }) => Fx<{ readonly authorizationUrl: string }>;\n readonly complete: (\n input: CallbackInput & { readonly codeVerifier: string },\n ) => Fx<IssuedCredential>;\n readonly refresh: (credential: Credential) => Fx<IssuedCredential>;\n readonly revoke?: (credential: Credential) => Fx<void>;\n}\n\n/** Marketplace-style installs (Vercel integrations) that redirect but are not OAuth. */\nexport interface IntegrationAuth {\n readonly label: string;\n readonly start: (input: {\n readonly state: string;\n readonly callbackUrl: string;\n }) => Fx<{ readonly authorizationUrl: string }>;\n readonly complete: (input: CallbackInput) => Fx<IssuedCredential>;\n readonly refresh?: (credential: Credential) => Fx<IssuedCredential>;\n readonly revoke?: (credential: Credential) => Fx<void>;\n}\n\nexport interface Definition<Context = unknown> {\n readonly id: string;\n readonly name: string;\n /**\n * Hostname suffixes of the nameservers this provider operates, e.g. `ns.cloudflare.com`.\n * `Connect.discover` names the provider as a domain's host when every authoritative nameserver\n * ends in one of them.\n */\n readonly nameservers?: ReadonlyArray<string>;\n readonly auth: {\n readonly token?: TokenAuth;\n readonly oauth?: OAuthAuth;\n readonly integration?: IntegrationAuth;\n };\n /** Schema for the account/zone context stored on authorizations and attachments. */\n readonly context: Schema.Codec<Context, unknown, never, never>;\n /** Tag written into every stored context envelope, e.g. `cloudflare.v1`; bump it with the schema. */\n readonly contextVersion: string;\n /** Upgrade an envelope written under an older `contextVersion`; absent means `Unsupported`. */\n readonly migrateContext?: (envelope: Envelope) => Fx<Context>;\n readonly session: (credential: Credential) => Session;\n}\n\n/** How provider context is persisted: the definition's version tag beside the encoded value. */\nexport const Envelope = Schema.Struct({ version: Schema.String, value: Schema.Unknown });\nexport type Envelope = typeof Envelope.Type;\n\n/** Wrap a context value for storage; a value the schema rejects fails `InvalidInput`. */\nexport const encodeContext = <Context>(\n definition: Definition<Context>,\n value: Context,\n): Effect.Effect<Envelope, Errors.DomainKitError> =>\n Schema.encodeEffect(definition.context)(value).pipe(\n Effect.map((encoded) => ({ version: definition.contextVersion, value: encoded })),\n Effect.mapError(\n (cause) =>\n new Errors.DomainKitError({\n reason: new Reason.InvalidInput({ message: cause.message, field: \"context\" }),\n }),\n ),\n );\n\nexport type AuthMethod = \"token\" | \"oauth\" | \"integration\";\n\n/** Validates a definition: an id and at least one auth method. Throws `DomainKit.Error` otherwise. */\nexport const make = <Context>(definition: Definition<Context>): Definition<Context> => {\n const invalid = (message: string, field: string) =>\n new Errors.DomainKitError({\n reason: new Reason.InvalidInput({ message, field }),\n });\n if (!/^[a-z0-9][a-z0-9-]*$/.test(definition.id)) {\n throw invalid(\n `Provider id ${JSON.stringify(definition.id)} must be lowercase kebab-case`,\n \"id\",\n );\n }\n if (methods(definition).length === 0) {\n throw invalid(`Provider ${definition.id} declares no auth method`, \"auth\");\n }\n if (definition.contextVersion.length === 0) {\n throw invalid(`Provider ${definition.id} needs a contextVersion`, \"contextVersion\");\n }\n return definition;\n};\n\n/** The kinds alone, in the same order `describeMethods` puts them: interactive first, token last. */\nexport const methods = (definition: Definition): ReadonlyArray<AuthMethod> =>\n describeMethods(definition).map((method) => method.kind);\n\n/**\n * Pick the most specific zone among `targets` for `domain`: exactly one match resolves, several\n * matches at the same depth need a selection, none is `NotFound`.\n */\nexport const resolveAmong = (\n domain: DomainName.Model,\n targets: ReadonlyArray<Target>,\n): Resolution => {\n for (const candidate of DomainName.candidates(domain)) {\n const matches = targets.filter((target) => target.zone === candidate);\n const [only] = matches;\n if (matches.length === 1 && only !== undefined) return Resolution.Resolved({ target: only });\n if (matches.length > 1) return Resolution.SelectionRequired({ candidates: matches });\n }\n return Resolution.NotFound();\n};\n\n/**\n * Decode a stored envelope: the current version decodes with `context`, an older one goes\n * through `migrateContext`, anything else fails `Unsupported`.\n */\nexport const decodeContext = <Context>(\n definition: Definition<Context>,\n stored: unknown,\n): Effect.Effect<Context, Errors.DomainKitError> =>\n Errors.decode(Envelope, stored, \"context\").pipe(\n Effect.flatMap((envelope) => {\n if (envelope.version === definition.contextVersion) {\n return Errors.decode(definition.context, envelope.value, \"context\");\n }\n if (definition.migrateContext !== undefined) return definition.migrateContext(envelope);\n return Errors.fail(\n new Reason.Unsupported({\n provider: definition.id,\n operation: \"context\",\n message: `${definition.id} context version ${envelope.version} is not supported (current ${definition.contextVersion})`,\n }),\n );\n }),\n );\n\nexport {\n type AsyncCredential,\n type AsyncDefinition,\n type AsyncDns,\n type AsyncIntegrationAuth,\n type AsyncIssuedCredential,\n type AsyncOAuthAuth,\n type AsyncSession,\n type AsyncTokenAuth,\n fromAsync,\n} from \"./internal/provider-async.ts\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA+CA,MAAa,aAAa,KAAK,WAAuB;AAoDtD,MAAa,aAAqD,WAMhD;CAChB,OAAO,MAAM;CACb,GAAI,MAAM,YAAY,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS,MAAM,QAAQ;CAChE,sBAAsB,MAAM;CAC5B,QAAQ,MAAM;CACd,eAAe,WACb,kBAAkB,MAAM,QAAQ,MAAM,CAAC,CAAC,KAAK,OAAO,QAAQ,MAAM,YAAY,CAAC;AACnF;AAEA,MAAM,qBACJ,QACA,WACwE;CACxE,MAAM,MAA8B,CAAC;CACrC,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,MAAM,GAC/C,IAAI,UAAU,KAAA,GAAW,IAAI,QAAQ,SAAS,MAAM,KAAK;CAE3D,OAAOA,OAAc,QAAQ,KAAK,QAAQ;AAC5C;;AAiBA,MAAa,eAAe,SAC1B,OAAO,QAAQ,KAAK,OAAO,MAAM,CAAC,CAAC,KAAK,CAAC,MAAM,WAAW;CACxD,MAAM,MAAO,MAAqC;CAClD,OAAO;EACL;EACA,UAAU,IAAI,SAAS,eAAe;EACtC,QAAQ,IAAI,SAAS;CACvB;AACF,CAAC;;;;;AAWH,MAAa,mBAAmB,eAA4D;CAC1F,MAAM,QAAiC,CAAC;CACxC,IAAI,WAAW,KAAK,UAAU,KAAA,GAC5B,MAAM,KAAK;EAAE,MAAM;EAAS,OAAO,WAAW,KAAK,MAAM;EAAO,SAAS;EAAM,QAAQ;CAAK,CAAC;CAE/F,IAAI,WAAW,KAAK,gBAAgB,KAAA,GAClC,MAAM,KAAK;EACT,MAAM;EACN,OAAO,WAAW,KAAK,YAAY;EACnC,SAAS;EACT,QAAQ;CACV,CAAC;CAEH,IAAI,WAAW,KAAK,UAAU,KAAA,GAC5B,MAAM,KAAK;EACT,MAAM;EACN,OAAO,WAAW,KAAK,MAAM;EAC7B,SAAS,WAAW,KAAK,MAAM,WAAW;EAC1C,QAAQ,YAAY,WAAW,KAAK,KAAK;CAC3C,CAAC;CAEH,OAAO;AACT;;AA6DA,MAAa,WAAW,OAAO,OAAO;CAAE,SAAS,OAAO;CAAQ,OAAO,OAAO;AAAQ,CAAC;;AAIvF,MAAa,iBACX,YACA,UAEA,OAAO,aAAa,WAAW,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,KAC7C,OAAO,KAAK,aAAa;CAAE,SAAS,WAAW;CAAgB,OAAO;AAAQ,EAAE,GAChF,OAAO,UACJ,UACC,IAAIC,eAAsB,EACxB,QAAQ,IAAIC,aAAoB;CAAE,SAAS,MAAM;CAAS,OAAO;AAAU,CAAC,EAC9E,CAAC,CACL,CACF;;AAKF,MAAa,QAAiB,eAAyD;CACrF,MAAM,WAAW,SAAiB,UAChC,IAAID,eAAsB,EACxB,QAAQ,IAAIC,aAAoB;EAAE;EAAS;CAAM,CAAC,EACpD,CAAC;CACH,IAAI,CAAC,uBAAuB,KAAK,WAAW,EAAE,GAC5C,MAAM,QACJ,eAAe,KAAK,UAAU,WAAW,EAAE,EAAE,gCAC7C,IACF;CAEF,IAAI,QAAQ,UAAU,CAAC,CAAC,WAAW,GACjC,MAAM,QAAQ,YAAY,WAAW,GAAG,2BAA2B,MAAM;CAE3E,IAAI,WAAW,eAAe,WAAW,GACvC,MAAM,QAAQ,YAAY,WAAW,GAAG,0BAA0B,gBAAgB;CAEpF,OAAO;AACT;;AAGA,MAAa,WAAW,eACtB,gBAAgB,UAAU,CAAC,CAAC,KAAK,WAAW,OAAO,IAAI;;;;;AAMzD,MAAa,gBACX,QACA,YACe;CACf,KAAK,MAAM,aAAaC,WAAsB,MAAM,GAAG;EACrD,MAAM,UAAU,QAAQ,QAAQ,WAAW,OAAO,SAAS,SAAS;EACpE,MAAM,CAAC,QAAQ;EACf,IAAI,QAAQ,WAAW,KAAK,SAAS,KAAA,GAAW,OAAO,WAAW,SAAS,EAAE,QAAQ,KAAK,CAAC;EAC3F,IAAI,QAAQ,SAAS,GAAG,OAAO,WAAW,kBAAkB,EAAE,YAAY,QAAQ,CAAC;CACrF;CACA,OAAO,WAAW,SAAS;AAC7B;;;;;AAMA,MAAa,iBACX,YACA,WAEAH,OAAc,UAAU,QAAQ,SAAS,CAAC,CAAC,KACzC,OAAO,SAAS,aAAa;CAC3B,IAAI,SAAS,YAAY,WAAW,gBAClC,OAAOA,OAAc,WAAW,SAAS,SAAS,OAAO,SAAS;CAEpE,IAAI,WAAW,mBAAmB,KAAA,GAAW,OAAO,WAAW,eAAe,QAAQ;CACtF,OAAOI,KACL,IAAIC,YAAmB;EACrB,UAAU,WAAW;EACrB,WAAW;EACX,SAAS,GAAG,WAAW,GAAG,mBAAmB,SAAS,QAAQ,6BAA6B,WAAW,eAAe;CACvH,CAAC,CACH;AACF,CAAC,CACH"}
|
|
1
|
+
{"version":3,"file":"Provider.mjs","names":["Errors.decode","Errors.DomainKitError","Reason.InvalidInput","DomainName.candidates","Errors.fail","Reason.Unsupported"],"sources":["../src/Provider.ts"],"sourcesContent":["/**\n * One declarative definition per DNS provider. Auth methods are optional cases, so a provider that\n * only issues personal tokens declares `token` and nothing else. The definition drives connection\n * routes, refresh, the UI method catalog, and rebuilding a session from stored context.\n */\nimport { Data, type DateTime, Effect, Redacted, Schema } from \"effect\";\n\nimport type * as DnsRecord from \"./DnsRecord.ts\";\nimport * as Errors from \"./internal/error.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as DomainName from \"./DomainName.ts\";\nimport type { Capability } from \"./Storage.ts\";\n\nexport type Fx<A> = Effect.Effect<A, Errors.DomainKitError>;\n\n/** A record as the provider reports it, with the id needed to read or delete it later. */\nexport interface ObservedRecord {\n readonly record: DnsRecord.Observed;\n readonly providerRecordId: string | null;\n}\n\n/** What a session can do inside one zone. The only thing planning and applying touch. */\nexport interface Dns {\n readonly list: (zone: string) => Fx<ReadonlyArray<ObservedRecord>>;\n /** Must return the provider's id for the new record; cleanup deletes by that id. */\n readonly create: (\n zone: string,\n record: DnsRecord.Model,\n ) => Fx<{ readonly providerRecordId: string }>;\n readonly get: (zone: string, providerRecordId: string) => Fx<DnsRecord.Observed | null>;\n readonly delete: (zone: string, providerRecordId: string) => Fx<void>;\n}\n\n/** A zone the credential can reach, with the provider identity needed to address it later. */\nexport interface Target {\n readonly zone: string;\n readonly context: unknown;\n readonly label: string;\n /** The zone's nameservers as the provider reports them; `Connect.discover` matches on them. */\n readonly nameservers?: ReadonlyArray<string>;\n}\n\nexport type Resolution = Data.TaggedEnum<{\n Resolved: { readonly target: Target };\n SelectionRequired: { readonly candidates: ReadonlyArray<Target> };\n NotFound: {};\n}>;\nexport const Resolution = Data.taggedEnum<Resolution>();\n\n/** A credential bound to provider account context. */\nexport interface Session {\n readonly listTargets: () => Fx<ReadonlyArray<Target>>;\n readonly resolveTarget: (domain: string) => Fx<Resolution>;\n readonly dns: (target: Target) => Dns;\n /** Capabilities actually held; promoted onto the authorization after first use. */\n readonly capabilities: () => Fx<ReadonlyArray<Capability>>;\n}\n\n/**\n * Secret material plus the account context needed to rebuild a session. `secret` is opaque to\n * DomainKit: a provider may pack an access token and a refresh token into it.\n */\nexport interface Credential {\n readonly secret: Redacted.Redacted<string>;\n readonly context: unknown;\n}\n\n/** A credential the provider just issued or refreshed; `null` expiry means it does not expire. */\nexport interface IssuedCredential extends Credential {\n readonly expiresAt: DateTime.Utc | null;\n /**\n * What the provider calls the account this credential reaches, so a UI names the connection\n * without a provider call. Omit it, or answer `null`, when the provider cannot name one: a\n * credential that spans several accounts has no single name.\n */\n readonly label?: string | null;\n}\n\n/** Raw token-method input keyed by the provider's declared field names. */\nexport type TokenValues = Readonly<Record<string, Redacted.Redacted<string> | undefined>>;\n\nexport interface TokenAuth {\n readonly label: string;\n /** Where the customer creates the token; shown in the UI. */\n readonly docsUrl?: string;\n readonly requiredCapabilities: ReadonlyArray<Capability>;\n /**\n * The values this method needs, declared once so forms render from it: secrets as\n * `Schema.RedactedFromValue(Schema.String)`, optional ones through `Schema.optionalKey`.\n */\n readonly fields: Schema.Struct<Schema.Struct.Fields>;\n /** Validate the values (already decoded with `fields`) and return account context. */\n readonly authenticate: (values: TokenValues) => Fx<IssuedCredential>;\n}\n\n/** Build a `TokenAuth` whose `authenticate` receives the record decoded by `fields`. */\n/** Field schemas whose decoding needs no services, so `authenticate` stays a plain `Fx`. */\nexport type TokenFieldSchemas = { readonly [name: string]: Schema.ConstraintDecoder<unknown> };\n\nexport const tokenAuth = <const Fields extends TokenFieldSchemas>(input: {\n readonly label: string;\n readonly docsUrl?: string;\n readonly requiredCapabilities: ReadonlyArray<Capability>;\n readonly fields: Schema.Struct<Fields>;\n readonly authenticate: (values: Schema.Struct<Fields>[\"Type\"]) => Fx<IssuedCredential>;\n}): TokenAuth => ({\n label: input.label,\n ...(input.docsUrl === undefined ? {} : { docsUrl: input.docsUrl }),\n requiredCapabilities: input.requiredCapabilities,\n fields: input.fields as Schema.Struct<Schema.Struct.Fields>,\n authenticate: (values) =>\n decodeTokenValues(input.fields, values).pipe(Effect.flatMap(input.authenticate)),\n});\n\nconst decodeTokenValues = <Fields extends TokenFieldSchemas>(\n fields: Schema.Struct<Fields>,\n values: TokenValues,\n): Effect.Effect<Schema.Struct<Fields>[\"Type\"], Errors.DomainKitError> => {\n const raw: Record<string, string> = {};\n for (const [name, value] of Object.entries(values)) {\n if (value !== undefined) raw[name] = Redacted.value(value);\n }\n return Errors.decode(fields, raw, \"method\");\n};\n\nexport interface FieldDescriptor {\n readonly name: string;\n readonly required: boolean;\n readonly secret: boolean;\n}\n\n/** What a UI needs to render one auth method; `fields` is `null` for interactive methods. */\nexport interface MethodDescriptor {\n readonly kind: AuthMethod;\n readonly label: string;\n readonly docsUrl: string | null;\n readonly fields: ReadonlyArray<FieldDescriptor> | null;\n}\n\n/** Field descriptors derived from `TokenAuth.fields`: optional keys are not required, `Redacted` values are secret. */\nexport const tokenFields = (auth: TokenAuth): ReadonlyArray<FieldDescriptor> =>\n Object.entries(auth.fields.fields).map(([name, field]) => {\n const ast = (field as { readonly ast: FieldAst }).ast;\n return {\n name,\n required: ast.context?.isOptional !== true,\n secret: ast._tag === \"Declaration\",\n };\n });\n\ninterface FieldAst {\n readonly _tag: string;\n readonly context?: { readonly isOptional?: boolean } | undefined;\n}\n\n/**\n * Every method a provider offers, in the order a UI should present them: the interactive ones a\n * customer clicks through first, then the token they have to go and fetch themselves.\n */\nexport const describeMethods = (definition: Definition): ReadonlyArray<MethodDescriptor> => {\n const found: Array<MethodDescriptor> = [];\n if (definition.auth.oauth !== undefined) {\n found.push({ kind: \"oauth\", label: definition.auth.oauth.label, docsUrl: null, fields: null });\n }\n if (definition.auth.integration !== undefined) {\n found.push({\n kind: \"integration\",\n label: definition.auth.integration.label,\n docsUrl: null,\n fields: null,\n });\n }\n if (definition.auth.token !== undefined) {\n found.push({\n kind: \"token\",\n label: definition.auth.token.label,\n docsUrl: definition.auth.token.docsUrl ?? null,\n fields: tokenFields(definition.auth.token),\n });\n }\n return found;\n};\n\nexport interface CallbackInput {\n readonly code: string;\n /** The redirect URI registered at start; token exchanges echo it. */\n readonly callbackUrl: string;\n /** Every query parameter the provider appended to the callback. */\n readonly params: Readonly<Record<string, string>>;\n}\n\nexport interface OAuthAuth {\n /** Optional setup requirements; absence makes offline registration settings unsupported. */\n readonly registration?: {\n readonly clientAuth: \"none\" | \"client_secret_basic\" | \"client_secret_post\";\n readonly pkce: \"S256\";\n };\n readonly label: string;\n readonly scopes: ReadonlyArray<string>;\n readonly start: (input: {\n readonly state: string;\n readonly callbackUrl: string;\n readonly codeChallenge: string;\n }) => Fx<{ readonly authorizationUrl: string }>;\n readonly complete: (\n input: CallbackInput & { readonly codeVerifier: string },\n ) => Fx<IssuedCredential>;\n readonly refresh: (credential: Credential) => Fx<IssuedCredential>;\n readonly revoke?: (credential: Credential) => Fx<void>;\n}\n\n/** Marketplace-style installs (Vercel integrations) that redirect but are not OAuth. */\nexport interface IntegrationAuth {\n /** Authentication used by the integration code exchange, without credentials. */\n readonly registration?: { readonly clientAuth: \"client_secret_post\" };\n readonly label: string;\n readonly start: (input: {\n readonly state: string;\n readonly callbackUrl: string;\n }) => Fx<{ readonly authorizationUrl: string }>;\n readonly complete: (input: CallbackInput) => Fx<IssuedCredential>;\n readonly refresh?: (credential: Credential) => Fx<IssuedCredential>;\n readonly revoke?: (credential: Credential) => Fx<void>;\n}\n\nexport interface Definition<Context = unknown> {\n readonly id: string;\n readonly name: string;\n /**\n * Hostname suffixes of the nameservers this provider operates, e.g. `ns.cloudflare.com`.\n * `Connect.discover` names the provider as a domain's host when every authoritative nameserver\n * ends in one of them.\n */\n readonly nameservers?: ReadonlyArray<string>;\n readonly auth: {\n readonly token?: TokenAuth;\n readonly oauth?: OAuthAuth;\n readonly integration?: IntegrationAuth;\n };\n /** Schema for the account/zone context stored on authorizations and attachments. */\n readonly context: Schema.Codec<Context, unknown, never, never>;\n /** Tag written into every stored context envelope, e.g. `cloudflare.v1`; bump it with the schema. */\n readonly contextVersion: string;\n /** Upgrade an envelope written under an older `contextVersion`; absent means `Unsupported`. */\n readonly migrateContext?: (envelope: Envelope) => Fx<Context>;\n readonly session: (credential: Credential) => Session;\n}\n\n/** How provider context is persisted: the definition's version tag beside the encoded value. */\nexport const Envelope = Schema.Struct({ version: Schema.String, value: Schema.Unknown });\nexport type Envelope = typeof Envelope.Type;\n\n/** Wrap a context value for storage; a value the schema rejects fails `InvalidInput`. */\nexport const encodeContext = <Context>(\n definition: Definition<Context>,\n value: Context,\n): Effect.Effect<Envelope, Errors.DomainKitError> =>\n Schema.encodeEffect(definition.context)(value).pipe(\n Effect.map((encoded) => ({ version: definition.contextVersion, value: encoded })),\n Effect.mapError(\n (cause) =>\n new Errors.DomainKitError({\n reason: new Reason.InvalidInput({ message: cause.message, field: \"context\" }),\n }),\n ),\n );\n\nexport type AuthMethod = \"token\" | \"oauth\" | \"integration\";\n\n/** Validates a definition: an id and at least one auth method. Throws `DomainKit.Error` otherwise. */\nexport const make = <Context>(definition: Definition<Context>): Definition<Context> => {\n const invalid = (message: string, field: string) =>\n new Errors.DomainKitError({\n reason: new Reason.InvalidInput({ message, field }),\n });\n if (!/^[a-z0-9][a-z0-9-]*$/.test(definition.id)) {\n throw invalid(\n `Provider id ${JSON.stringify(definition.id)} must be lowercase kebab-case`,\n \"id\",\n );\n }\n if (methods(definition).length === 0) {\n throw invalid(`Provider ${definition.id} declares no auth method`, \"auth\");\n }\n if (definition.contextVersion.length === 0) {\n throw invalid(`Provider ${definition.id} needs a contextVersion`, \"contextVersion\");\n }\n return definition;\n};\n\n/** The kinds alone, in the same order `describeMethods` puts them: interactive first, token last. */\nexport const methods = (definition: Definition): ReadonlyArray<AuthMethod> =>\n describeMethods(definition).map((method) => method.kind);\n\n/**\n * Pick the most specific zone among `targets` for `domain`: exactly one match resolves, several\n * matches at the same depth need a selection, none is `NotFound`.\n */\nexport const resolveAmong = (\n domain: DomainName.Model,\n targets: ReadonlyArray<Target>,\n): Resolution => {\n for (const candidate of DomainName.candidates(domain)) {\n const matches = targets.filter((target) => target.zone === candidate);\n const [only] = matches;\n if (matches.length === 1 && only !== undefined) return Resolution.Resolved({ target: only });\n if (matches.length > 1) return Resolution.SelectionRequired({ candidates: matches });\n }\n return Resolution.NotFound();\n};\n\n/**\n * Decode a stored envelope: the current version decodes with `context`, an older one goes\n * through `migrateContext`, anything else fails `Unsupported`.\n */\nexport const decodeContext = <Context>(\n definition: Definition<Context>,\n stored: unknown,\n): Effect.Effect<Context, Errors.DomainKitError> =>\n Errors.decode(Envelope, stored, \"context\").pipe(\n Effect.flatMap((envelope) => {\n if (envelope.version === definition.contextVersion) {\n return Errors.decode(definition.context, envelope.value, \"context\");\n }\n if (definition.migrateContext !== undefined) return definition.migrateContext(envelope);\n return Errors.fail(\n new Reason.Unsupported({\n provider: definition.id,\n operation: \"context\",\n message: `${definition.id} context version ${envelope.version} is not supported (current ${definition.contextVersion})`,\n }),\n );\n }),\n );\n\nexport {\n type AsyncCredential,\n type AsyncDefinition,\n type AsyncDns,\n type AsyncIntegrationAuth,\n type AsyncIssuedCredential,\n type AsyncOAuthAuth,\n type AsyncSession,\n type AsyncTokenAuth,\n fromAsync,\n} from \"./internal/provider-async.ts\";\n"],"mappings":"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"}
|