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/Vercel.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { Unauthenticated } from "./Reason.mjs";
|
|
|
4
4
|
import { fail } from "./internal/error.mjs";
|
|
5
5
|
import { make, resolveAmong, tokenAuth } from "./Provider.mjs";
|
|
6
6
|
import { rejectedToken } from "./internal/http.mjs";
|
|
7
|
-
import { resolve } from "./internal/config.mjs";
|
|
7
|
+
import { endpoint, nonempty, resolve, secret } from "./internal/config.mjs";
|
|
8
8
|
import { dns, exchangeCode, teams, user, zones } from "./internal/vercel/client.mjs";
|
|
9
9
|
import { Effect, Redacted, Schema } from "effect";
|
|
10
10
|
//#region src/Vercel.ts
|
|
@@ -24,11 +24,11 @@ const TokenFields = Schema.Struct({
|
|
|
24
24
|
const capabilities = ["dns:read", "dns:write"];
|
|
25
25
|
const provider = (options = {}) => {
|
|
26
26
|
const fetch = options.fetch ?? globalThis.fetch;
|
|
27
|
-
const baseUrl =
|
|
27
|
+
const baseUrl = options.baseUrl ?? "https://api.vercel.com";
|
|
28
28
|
const client = (token) => ({
|
|
29
29
|
token,
|
|
30
30
|
fetch,
|
|
31
|
-
baseUrl
|
|
31
|
+
baseUrl: baseUrl.replace(/\/$/, "")
|
|
32
32
|
});
|
|
33
33
|
const targetOf = (domain, label) => ({
|
|
34
34
|
zone: domain.name,
|
|
@@ -38,18 +38,28 @@ const provider = (options = {}) => {
|
|
|
38
38
|
});
|
|
39
39
|
const integration = options.integration === void 0 ? void 0 : integrationAuth(options.integration);
|
|
40
40
|
function integrationAuth(settings) {
|
|
41
|
-
const installOrigin =
|
|
41
|
+
const installOrigin = settings.installOrigin ?? "https://vercel.com";
|
|
42
|
+
const credentials = () => Effect.gen(function* () {
|
|
43
|
+
yield* endpoint(settings.installOrigin ?? "https://vercel.com", "integration.installOrigin");
|
|
44
|
+
yield* endpoint(options.baseUrl ?? "https://api.vercel.com", "baseUrl");
|
|
45
|
+
yield* nonempty(settings.slug, "integration.slug");
|
|
46
|
+
return {
|
|
47
|
+
clientId: yield* resolve(settings.clientId, "integration.clientId").pipe(Effect.flatMap((v) => nonempty(v, "integration.clientId"))),
|
|
48
|
+
clientSecret: yield* resolve(settings.clientSecret, "integration.clientSecret").pipe(Effect.flatMap((v) => secret(v, "integration.clientSecret")))
|
|
49
|
+
};
|
|
50
|
+
});
|
|
42
51
|
return {
|
|
43
52
|
label: "Install the Vercel integration",
|
|
44
|
-
|
|
45
|
-
|
|
53
|
+
registration: { clientAuth: "client_secret_post" },
|
|
54
|
+
start: (input) => Effect.gen(function* () {
|
|
55
|
+
yield* credentials();
|
|
56
|
+
const authorizationUrl = new URL(`${installOrigin.replace(/\/$/, "")}/integrations/${encodeURIComponent(settings.slug)}/new`);
|
|
46
57
|
authorizationUrl.searchParams.set("source", "external");
|
|
47
58
|
authorizationUrl.searchParams.set("state", input.state);
|
|
48
|
-
return
|
|
49
|
-
},
|
|
59
|
+
return { authorizationUrl: authorizationUrl.toString() };
|
|
60
|
+
}),
|
|
50
61
|
complete: (input) => Effect.gen(function* () {
|
|
51
|
-
const clientId = yield*
|
|
52
|
-
const clientSecret = yield* resolve(settings.clientSecret, "integration.clientSecret");
|
|
62
|
+
const { clientId, clientSecret } = yield* credentials();
|
|
53
63
|
const token = yield* exchangeCode({
|
|
54
64
|
options: client(""),
|
|
55
65
|
clientId,
|
package/dist/Vercel.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Vercel.mjs","names":["Client.exchangeCode","Errors.fail","Reason.Unauthenticated","Provider.make","Provider.tokenAuth","Client.user","Client.zones","teams","Client.teams","DomainName.decode","Provider.resolveAmong","Client.dns"],"sources":["../src/Vercel.ts"],"sourcesContent":["/** Vercel: personal tokens and marketplace integrations. */\nimport { type Config, Effect, Redacted, Schema } from \"effect\";\n\nimport * as Errors from \"./internal/error.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as DomainName from \"./DomainName.ts\";\nimport { resolve } from \"./internal/config.ts\";\nimport { type Fetch, rejectedToken } from \"./internal/http.ts\";\nimport * as Client from \"./internal/vercel/client.ts\";\nimport type * as Protocol from \"./internal/vercel/protocol.ts\";\nimport * as Provider from \"./Provider.ts\";\n\n/** `teamId` is `null` for a personal account; targets carry the team that owns the zone. */\nexport const TeamContext = Schema.Struct({ teamId: Schema.NullOr(Schema.String) });\nexport type TeamContext = typeof TeamContext.Type;\n\n/** Token-method input: the access token, and a team id to scope the connection to one team. */\nexport const TokenFields = Schema.Struct({\n token: Schema.RedactedFromValue(Schema.String),\n teamId: Schema.optionalKey(Schema.String),\n});\n\nexport interface Options {\n /** Omit to offer tokens only. */\n readonly integration?: {\n readonly clientId: string | Config.Config<string>;\n readonly clientSecret: Redacted.Redacted<string> | Config.Config<Redacted.Redacted<string>>;\n readonly slug: string;\n /**\n * Origin of the install page, which Vercel serves at `/integrations/{slug}/new` beneath it.\n * Default `https://vercel.com`; a stage points it at an emulator that mounts the same path.\n * It stays separate from `baseUrl` because in production these are different hosts:\n * `vercel.com` for the browser install, `api.vercel.com` for the code exchange and the REST API.\n */\n readonly installOrigin?: string;\n };\n readonly fetch?: Fetch;\n readonly baseUrl?: string;\n}\n\nconst capabilities = [\"dns:read\", \"dns:write\"] as const;\n\nexport const provider = (options: Options = {}): Provider.Definition<TeamContext> => {\n const fetch = options.fetch ?? globalThis.fetch;\n const baseUrl = (options.baseUrl ?? \"https://api.vercel.com\").replace(/\\/$/, \"\");\n const client = (token: string): Client.Options => ({ token, fetch, baseUrl });\n\n const targetOf = (domain: Protocol.Domain, label: string): Provider.Target => ({\n zone: domain.name,\n context: { teamId: domain.teamId } satisfies TeamContext,\n label: `${domain.name} (${label})`,\n nameservers: domain.nameservers.length > 0 ? domain.nameservers : domain.intendedNameservers,\n });\n\n const integration =\n options.integration === undefined ? undefined : integrationAuth(options.integration);\n\n function integrationAuth(\n settings: NonNullable<Options[\"integration\"]>,\n ): Provider.IntegrationAuth {\n const installOrigin = (settings.installOrigin ?? \"https://vercel.com\").replace(/\\/$/, \"\");\n return {\n label: \"Install the Vercel integration\",\n start: (input) => {\n const authorizationUrl = new URL(\n `${installOrigin}/integrations/${encodeURIComponent(settings.slug)}/new`,\n );\n authorizationUrl.searchParams.set(\"source\", \"external\");\n authorizationUrl.searchParams.set(\"state\", input.state);\n return Effect.succeed({ authorizationUrl: authorizationUrl.toString() });\n },\n complete: (input) =>\n Effect.gen(function* () {\n const clientId = yield* resolve(settings.clientId, \"integration.clientId\");\n const clientSecret = yield* resolve(settings.clientSecret, \"integration.clientSecret\");\n const token = yield* Client.exchangeCode({\n options: client(\"\"),\n clientId,\n clientSecret: Redacted.value(clientSecret),\n code: input.code,\n callbackUrl: input.callbackUrl,\n });\n const callbackTeam = input.params.teamId;\n if (callbackTeam !== undefined && callbackTeam !== token.team_id) {\n return yield* Errors.fail(\n new Reason.Unauthenticated({\n message: \"Vercel integration callback team does not match the installed team\",\n }),\n );\n }\n return {\n secret: Redacted.make(token.access_token),\n context: { teamId: token.team_id } satisfies TeamContext,\n expiresAt: null,\n };\n }),\n };\n }\n\n return Provider.make<TeamContext>({\n id: \"vercel\",\n name: \"Vercel\",\n nameservers: [\"vercel-dns.com\"],\n context: TeamContext,\n contextVersion: \"vercel.v1\",\n auth: {\n token: Provider.tokenAuth({\n label: \"Access token\",\n docsUrl: \"https://vercel.com/account/settings/tokens\",\n requiredCapabilities: capabilities,\n fields: TokenFields,\n authenticate: ({ token, teamId }) =>\n Client.user(client(Redacted.value(token))).pipe(\n Effect.map(() => ({\n secret: token,\n context: { teamId: teamId ?? null } satisfies TeamContext,\n expiresAt: null,\n })),\n rejectedToken,\n ),\n }),\n ...(integration === undefined ? {} : { integration }),\n },\n session: (credential) => {\n const clientOptions = client(Redacted.value(credential.secret));\n const context = Schema.decodeUnknownOption(TeamContext)(credential.context);\n const teamId = context._tag === \"Some\" ? context.value.teamId : null;\n const listTargets = (): Effect.Effect<\n ReadonlyArray<Provider.Target>,\n Errors.DomainKitError\n > =>\n teamId !== null\n ? Client.zones(clientOptions, teamId).pipe(\n Effect.map((domains) => domains.map((domain) => targetOf(domain, teamId))),\n )\n : Effect.gen(function* () {\n const personal = yield* Client.zones(clientOptions, null);\n const teams = yield* Client.teams(clientOptions).pipe(\n Effect.catchIf(\n (error) => error.reason._tag !== \"ProviderUnavailable\",\n () => Effect.succeed([]),\n ),\n );\n const perTeam = yield* Effect.forEach(teams, (team) =>\n Client.zones(clientOptions, team.id).pipe(\n Effect.map((domains) =>\n domains.map((domain) => targetOf(domain, team.name ?? team.slug)),\n ),\n ),\n );\n return [...personal.map((domain) => targetOf(domain, \"personal\")), ...perTeam.flat()];\n });\n return {\n capabilities: () => Effect.succeed(capabilities),\n listTargets,\n resolveTarget: (domain) =>\n Effect.gen(function* () {\n const name = yield* DomainName.decode(domain);\n const targets = yield* listTargets();\n return Provider.resolveAmong(name, targets);\n }),\n dns: (target) => {\n const scope = Schema.decodeUnknownOption(TeamContext)(target.context);\n return Client.dns(clientOptions, scope._tag === \"Some\" ? scope.value.teamId : teamId);\n },\n };\n },\n });\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Vercel.mjs","names":["Configuration.endpoint","Configuration.nonempty","Configuration.resolve","Configuration.secret","Client.exchangeCode","Errors.fail","Reason.Unauthenticated","Provider.make","Provider.tokenAuth","Client.user","Client.zones","teams","Client.teams","DomainName.decode","Provider.resolveAmong","Client.dns"],"sources":["../src/Vercel.ts"],"sourcesContent":["/** Vercel: personal tokens and marketplace integrations. */\nimport { type Config, Effect, Redacted, Schema } from \"effect\";\n\nimport * as Errors from \"./internal/error.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as DomainName from \"./DomainName.ts\";\nimport * as Configuration from \"./internal/config.ts\";\nimport { type Fetch, rejectedToken } from \"./internal/http.ts\";\nimport * as Client from \"./internal/vercel/client.ts\";\nimport type * as Protocol from \"./internal/vercel/protocol.ts\";\nimport * as Provider from \"./Provider.ts\";\n\n/** `teamId` is `null` for a personal account; targets carry the team that owns the zone. */\nexport const TeamContext = Schema.Struct({ teamId: Schema.NullOr(Schema.String) });\nexport type TeamContext = typeof TeamContext.Type;\n\n/** Token-method input: the access token, and a team id to scope the connection to one team. */\nexport const TokenFields = Schema.Struct({\n token: Schema.RedactedFromValue(Schema.String),\n teamId: Schema.optionalKey(Schema.String),\n});\n\nexport interface Options {\n /** Omit to offer tokens only. */\n readonly integration?: {\n readonly clientId: string | Config.Config<string>;\n readonly clientSecret: Redacted.Redacted<string> | Config.Config<Redacted.Redacted<string>>;\n readonly slug: string;\n /**\n * Origin of the install page, which Vercel serves at `/integrations/{slug}/new` beneath it.\n * Default `https://vercel.com`; a stage points it at an emulator that mounts the same path.\n * It stays separate from `baseUrl` because in production these are different hosts:\n * `vercel.com` for the browser install, `api.vercel.com` for the code exchange and the REST API.\n */\n readonly installOrigin?: string;\n };\n readonly fetch?: Fetch;\n readonly baseUrl?: string;\n}\n\nconst capabilities = [\"dns:read\", \"dns:write\"] as const;\n\nexport const provider = (options: Options = {}): Provider.Definition<TeamContext> => {\n const fetch = options.fetch ?? globalThis.fetch;\n const baseUrl = options.baseUrl ?? \"https://api.vercel.com\";\n const client = (token: string): Client.Options => ({\n token,\n fetch,\n baseUrl: baseUrl.replace(/\\/$/, \"\"),\n });\n\n const targetOf = (domain: Protocol.Domain, label: string): Provider.Target => ({\n zone: domain.name,\n context: { teamId: domain.teamId } satisfies TeamContext,\n label: `${domain.name} (${label})`,\n nameservers: domain.nameservers.length > 0 ? domain.nameservers : domain.intendedNameservers,\n });\n\n const integration =\n options.integration === undefined ? undefined : integrationAuth(options.integration);\n\n function integrationAuth(\n settings: NonNullable<Options[\"integration\"]>,\n ): Provider.IntegrationAuth {\n const installOrigin = settings.installOrigin ?? \"https://vercel.com\";\n const credentials = () =>\n Effect.gen(function* () {\n yield* Configuration.endpoint(\n settings.installOrigin ?? \"https://vercel.com\",\n \"integration.installOrigin\",\n );\n yield* Configuration.endpoint(options.baseUrl ?? \"https://api.vercel.com\", \"baseUrl\");\n yield* Configuration.nonempty(settings.slug, \"integration.slug\");\n const clientId = yield* Configuration.resolve(\n settings.clientId,\n \"integration.clientId\",\n ).pipe(Effect.flatMap((v) => Configuration.nonempty(v, \"integration.clientId\")));\n const clientSecret = yield* Configuration.resolve(\n settings.clientSecret,\n \"integration.clientSecret\",\n ).pipe(Effect.flatMap((v) => Configuration.secret(v, \"integration.clientSecret\")));\n return { clientId, clientSecret };\n });\n return {\n label: \"Install the Vercel integration\",\n registration: { clientAuth: \"client_secret_post\" },\n start: (input) =>\n Effect.gen(function* () {\n yield* credentials();\n const authorizationUrl = new URL(\n `${installOrigin.replace(/\\/$/, \"\")}/integrations/${encodeURIComponent(settings.slug)}/new`,\n );\n authorizationUrl.searchParams.set(\"source\", \"external\");\n authorizationUrl.searchParams.set(\"state\", input.state);\n return { authorizationUrl: authorizationUrl.toString() };\n }),\n complete: (input) =>\n Effect.gen(function* () {\n const { clientId, clientSecret } = yield* credentials();\n const token = yield* Client.exchangeCode({\n options: client(\"\"),\n clientId,\n clientSecret: Redacted.value(clientSecret),\n code: input.code,\n callbackUrl: input.callbackUrl,\n });\n const callbackTeam = input.params.teamId;\n if (callbackTeam !== undefined && callbackTeam !== token.team_id) {\n return yield* Errors.fail(\n new Reason.Unauthenticated({\n message: \"Vercel integration callback team does not match the installed team\",\n }),\n );\n }\n return {\n secret: Redacted.make(token.access_token),\n context: { teamId: token.team_id } satisfies TeamContext,\n expiresAt: null,\n };\n }),\n };\n }\n\n return Provider.make<TeamContext>({\n id: \"vercel\",\n name: \"Vercel\",\n nameservers: [\"vercel-dns.com\"],\n context: TeamContext,\n contextVersion: \"vercel.v1\",\n auth: {\n token: Provider.tokenAuth({\n label: \"Access token\",\n docsUrl: \"https://vercel.com/account/settings/tokens\",\n requiredCapabilities: capabilities,\n fields: TokenFields,\n authenticate: ({ token, teamId }) =>\n Client.user(client(Redacted.value(token))).pipe(\n Effect.map(() => ({\n secret: token,\n context: { teamId: teamId ?? null } satisfies TeamContext,\n expiresAt: null,\n })),\n rejectedToken,\n ),\n }),\n ...(integration === undefined ? {} : { integration }),\n },\n session: (credential) => {\n const clientOptions = client(Redacted.value(credential.secret));\n const context = Schema.decodeUnknownOption(TeamContext)(credential.context);\n const teamId = context._tag === \"Some\" ? context.value.teamId : null;\n const listTargets = (): Effect.Effect<\n ReadonlyArray<Provider.Target>,\n Errors.DomainKitError\n > =>\n teamId !== null\n ? Client.zones(clientOptions, teamId).pipe(\n Effect.map((domains) => domains.map((domain) => targetOf(domain, teamId))),\n )\n : Effect.gen(function* () {\n const personal = yield* Client.zones(clientOptions, null);\n const teams = yield* Client.teams(clientOptions).pipe(\n Effect.catchIf(\n (error) => error.reason._tag !== \"ProviderUnavailable\",\n () => Effect.succeed([]),\n ),\n );\n const perTeam = yield* Effect.forEach(teams, (team) =>\n Client.zones(clientOptions, team.id).pipe(\n Effect.map((domains) =>\n domains.map((domain) => targetOf(domain, team.name ?? team.slug)),\n ),\n ),\n );\n return [...personal.map((domain) => targetOf(domain, \"personal\")), ...perTeam.flat()];\n });\n return {\n capabilities: () => Effect.succeed(capabilities),\n listTargets,\n resolveTarget: (domain) =>\n Effect.gen(function* () {\n const name = yield* DomainName.decode(domain);\n const targets = yield* listTargets();\n return Provider.resolveAmong(name, targets);\n }),\n dns: (target) => {\n const scope = Schema.decodeUnknownOption(TeamContext)(target.context);\n return Client.dns(clientOptions, scope._tag === \"Some\" ? scope.value.teamId : teamId);\n },\n };\n },\n });\n};\n"],"mappings":"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"}
|
package/dist/Verify.mjs
CHANGED
|
@@ -152,7 +152,7 @@ const quorumStatus = (quorum, statuses) => {
|
|
|
152
152
|
};
|
|
153
153
|
const overallOf = (requirements, host) => {
|
|
154
154
|
if (requirements.some(({ status }) => status === "mismatch") || host.some(({ status }) => status === "failed")) return "failed";
|
|
155
|
-
return requirements.length
|
|
155
|
+
return requirements.length > 0 && requirements.every(({ status }) => status === "satisfied") && host.every(({ status }) => status === "ok") ? "ready" : "pending";
|
|
156
156
|
};
|
|
157
157
|
/** The identity of a requirement set: type, name, data, policy, and constraint, independent of labels and order. */
|
|
158
158
|
const requirementSetKey = (requirements) => requirements.map(({ record }) => `${record._tag} ${record.name} ${data(record)} ${record.policy} ${record._tag === "TXT" ? record.constraint ?? "" : ""}`).sort().join("\n");
|
|
@@ -177,14 +177,15 @@ const make = Effect.gen(function* () {
|
|
|
177
177
|
evidence
|
|
178
178
|
}))));
|
|
179
179
|
const host = yield* decode$1(StoredHost, row.host, "host");
|
|
180
|
+
const overall = overallOf(requirements, host);
|
|
180
181
|
return {
|
|
181
182
|
domain: row.domain,
|
|
182
183
|
attachmentId: row.attachmentId,
|
|
183
|
-
overall
|
|
184
|
+
overall,
|
|
184
185
|
requirements,
|
|
185
186
|
host,
|
|
186
187
|
checkedAt: row.checkedAt,
|
|
187
|
-
nextCheckAt: row.nextCheckAt
|
|
188
|
+
nextCheckAt: row.nextCheckAt ?? (overall === "ready" ? null : row.checkedAt)
|
|
188
189
|
};
|
|
189
190
|
});
|
|
190
191
|
/** Persist readiness, carrying the pending streak forward for the backoff ladder. */
|
package/dist/Verify.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Verify.mjs","names":["Storage.RequirementStatus","DnsRecord.data","DnsRecord.equals","DnsRecord.sameSet","Storage.Service","Connect.Service","Resolver.Service","DomainName.decode","Errors.decode","Principal.Service","Storage.Readiness","Errors.fail","Reason.InvalidInput"],"sources":["../src/Verify.ts"],"sourcesContent":["/**\n * Observe provider and public DNS for an attachment, persist per-requirement readiness, and say\n * when to look again. The host keeps its own state machine (and any provider-side verification\n * such as SES) and feeds that in as `HostEvidence`.\n */\nimport { Context, DateTime, Duration, Effect, Layer, Option, Schema } from \"effect\";\n\nimport * as Connect from \"./Connect.ts\";\nimport * as DnsRecord from \"./DnsRecord.ts\";\nimport * as Errors from \"./internal/error.ts\";\nimport { collision } from \"./internal/reconciliation.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as DomainName from \"./DomainName.ts\";\nimport type * as Plan from \"./Plan.ts\";\nimport * as Principal from \"./Principal.ts\";\nimport * as Resolver from \"./Resolver.ts\";\nimport * as Storage from \"./Storage.ts\";\n\n/**\n * Every piece of evidence carries `values`, what the observer returned for the requirement's name\n * and type (empty when nothing was found, the lookup timed out, or the resolver failed), and\n * `detail`, `null` when the requirement is satisfied and otherwise the mismatch summary or the\n * resolver's error text, so a host can render \"found X, expected Y\".\n */\n// Rows written before these fields existed decode with the empty values.\nconst Values = Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed([])));\nconst Detail = Schema.NullOr(Schema.String).pipe(\n Schema.withDecodingDefaultKey(Effect.succeed(null)),\n);\n\nexport class ProviderEvidence extends Schema.TaggedClass<ProviderEvidence>(\n \"@domainkit/Evidence/Provider\",\n)(\"Provider\", {\n provider: Schema.String,\n status: Storage.RequirementStatus,\n values: Values,\n detail: Detail,\n observedAt: Schema.DateTimeUtcFromString,\n}) {}\nexport class PublicDnsEvidence extends Schema.TaggedClass<PublicDnsEvidence>(\n \"@domainkit/Evidence/PublicDns\",\n)(\"PublicDns\", {\n resolver: Schema.String,\n status: Storage.RequirementStatus,\n values: Values,\n detail: Detail,\n observedAt: Schema.DateTimeUtcFromString,\n}) {}\n/** Anything the host knows that DomainKit cannot observe: SES identity status, a CDN cert, ... */\nexport class HostEvidence extends Schema.TaggedClass<HostEvidence>(\"@domainkit/Evidence/Host\")(\n \"Host\",\n {\n source: Schema.String,\n status: Schema.Literals([\"ok\", \"pending\", \"failed\"]),\n label: Schema.String,\n detail: Detail,\n observedAt: Schema.DateTimeUtcFromString,\n },\n) {}\nexport const Evidence = Schema.Union([ProviderEvidence, PublicDnsEvidence, HostEvidence]);\nexport type Evidence = typeof Evidence.Type;\n\nexport interface Requirement {\n /** `requirementKey(record)`: what a host pairs its own row against, by content. */\n readonly key: string;\n readonly operationId: Plan.OperationId | null;\n readonly record: DnsRecord.Model;\n readonly status: Storage.RequirementStatus;\n readonly evidence: ReadonlyArray<Evidence>;\n}\n\nexport interface Readiness {\n readonly domain: string;\n /** `null` for observe-only domains that have no attachment. */\n readonly attachmentId: string | null;\n readonly overall: Storage.Overall;\n readonly requirements: ReadonlyArray<Requirement>;\n readonly host: ReadonlyArray<HostEvidence>;\n readonly checkedAt: DateTime.Utc;\n readonly nextCheckAt: DateTime.Utc | null;\n}\n\ntype Fx<A> = Effect.Effect<A, Errors.DomainKitError, Principal.Service>;\n\nexport interface Interface {\n /**\n * What to observe, in order of precedence: `requirements` when supplied (an attached domain\n * without a provisioning receipt, or a domain with no attachment whose records the customer\n * applies by hand), else the latest provisioning receipt of the domain's attachment (records it\n * applied or found in place). Neither is `InvalidInput`. Provider evidence is added when the\n * attachment's session can be built; public DNS is always observed, and readiness is stored\n * per domain either way.\n */\n readonly observe: (input: {\n readonly domain: string;\n readonly requirements?: ReadonlyArray<DnsRecord.Model>;\n }) => Fx<Readiness>;\n /** Merge host evidence into stored readiness without re-observing DNS. */\n readonly attachEvidence: (input: {\n readonly domain: string;\n readonly evidence: ReadonlyArray<HostEvidence>;\n }) => Fx<Readiness>;\n readonly latest: (domain: string) => Fx<Readiness | null>;\n}\n\nexport class Service extends Context.Service<Service, Interface>()(\"@domainkit/Verify\") {}\n\n/**\n * A requirement's identity: the record's type, name, and data, and nothing else. It is what a host\n * pairs its own rows against readiness by, instead of trusting the order of the array.\n * `@domainkit/react`'s `Records.identity` is this function.\n */\nexport const requirementKey = (record: DnsRecord.Model): string =>\n [record._tag, record.name, DnsRecord.data(record)].join(\":\");\n\n/** Counts across a readiness's requirements. `observed` is false when there is no readiness yet. */\nexport interface Summary {\n readonly observed: boolean;\n readonly total: number;\n readonly satisfied: number;\n readonly missing: number;\n readonly mismatch: number;\n readonly unknown: number;\n}\n\n/**\n * How a readiness stands, as counts. Pure and total: a host renders \"3 of 4 found\" from the stored\n * fact without deciding what an absent readiness means, because `observed` says so. The parameter\n * is the least a value has to carry to be summarised, so core `Readiness` and the wire shape both\n * pass without naming a type between them.\n */\nexport const summary = (\n readiness: {\n readonly requirements: ReadonlyArray<{ readonly status: Storage.RequirementStatus }>;\n } | null,\n): Summary => {\n const statuses = readiness?.requirements.map(({ status }) => status) ?? [];\n const count = (status: Storage.RequirementStatus): number =>\n statuses.filter((candidate) => candidate === status).length;\n return {\n observed: readiness !== null,\n total: statuses.length,\n satisfied: count(\"satisfied\"),\n missing: count(\"missing\"),\n mismatch: count(\"mismatch\"),\n unknown: count(\"unknown\"),\n };\n};\n\nexport interface PolicyShape {\n /** Delay before the next check, given time since the first pending observation. Default ladder: 15s, 1m, 5m, 30m. */\n readonly backoff: (pendingForMs: number) => number;\n /** Which resolvers must agree for `satisfied`: default `any`. */\n readonly quorum: \"any\" | \"all\" | { readonly minimum: number };\n}\nexport const defaults: PolicyShape = {\n backoff: (pendingForMs) =>\n pendingForMs < 60_000\n ? 15_000\n : pendingForMs < 10 * 60_000\n ? 60_000\n : pendingForMs < 60 * 60_000\n ? 5 * 60_000\n : 30 * 60_000,\n quorum: \"any\",\n};\nexport class Policy extends Context.Reference<PolicyShape>(\"@domainkit/Verify/Policy\", {\n defaultValue: () => defaults,\n}) {}\n\n/** Why readiness was written: a DNS observation, or host evidence merged into the stored row. */\nexport type Cause = \"observe\" | \"evidence\";\n\nexport interface ReadinessChanged {\n /** The tenant the row was written under, from the `Principal` the write ran as. */\n readonly ownerId: string;\n readonly domain: string;\n readonly readiness: Readiness;\n readonly cause: Cause;\n}\n\nexport interface ObserverShape {\n /**\n * Called once per stored readiness, after the write. A host that projects readiness onto its own\n * rows, wakes a durable job at `nextCheckAt`, or notifies a customer hangs it here instead of\n * mirroring the fact at every call site.\n *\n * Readiness is stored per owner and domain, and an observer provided over `DomainKit.layer` is\n * scoped to the layer rather than to one request, so a host that serves more than one tenant\n * keys its projection by `ownerId` and `domain`: the same domain name can be attached in two\n * tenants at once.\n *\n * Events for one domain are not ordered against each other: two observations that overlap both\n * store, last write wins in `Storage`, and their callbacks can finish in either order. A\n * projection compares `readiness.checkedAt`, which is the moment the row was written, and keeps\n * the later one; `latest` remains the authority.\n */\n readonly readinessChanged: (event: ReadinessChanged) => Effect.Effect<void, unknown>;\n}\n\n/**\n * The host seam for \"DomainKit wrote readiness\". The default does nothing; provide one with\n * `Effect.provideService(Verify.Observer, ...)` or `Layer.succeed(Verify.Observer, ...)` over\n * `DomainKit.layer`, the same way `Policy` is overridden.\n *\n * It runs after `storage.readiness.put` returns, outside any transaction, because `Storage`\n * exposes none. A failure or defect in the observer is logged and swallowed: the observation is\n * already durable, and losing it because a projection failed would make the stored fact depend on\n * the host's side effects. A host that needs the projection to be reliable enqueues durable work\n * here rather than doing the work inline.\n */\nexport class Observer extends Context.Reference<ObserverShape>(\"@domainkit/Verify/Observer\", {\n defaultValue: (): ObserverShape => ({ readinessChanged: () => Effect.void }),\n}) {}\n\n// ---------------------------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------------------------\n\nconst StoredEvidence = Schema.Array(Evidence);\n\ninterface ProviderSide {\n readonly provider: string;\n readonly records: ReadonlyArray<DnsRecord.Observed>;\n}\nconst StoredHost = Schema.Array(HostEvidence);\n\n/** A requirement is satisfied only when its exact match also meets its safety constraints. */\nexport const statusAgainst = (\n record: DnsRecord.Model,\n observed: ReadonlyArray<DnsRecord.Observed>,\n): Storage.RequirementStatus => {\n if (collision(record, observed) !== undefined) return \"mismatch\";\n return observed.some((candidate) => DnsRecord.equals(candidate, record))\n ? \"satisfied\"\n : \"missing\";\n};\n\ninterface Observation {\n readonly status: Storage.RequirementStatus;\n readonly values: ReadonlyArray<string>;\n readonly detail: string | null;\n}\n\nconst render = (observed: DnsRecord.Observed): string =>\n observed._tag === \"Opaque\" ? JSON.stringify(observed.raw) : DnsRecord.data(observed);\n\n/** Status plus what the observer holds for the requirement's name and type. */\nconst observation = (\n record: DnsRecord.Model,\n observed: ReadonlyArray<DnsRecord.Observed>,\n): Observation => {\n const status = statusAgainst(record, observed);\n const values = observed.filter((candidate) => DnsRecord.sameSet(candidate, record)).map(render);\n const detail =\n status === \"satisfied\"\n ? null\n : values.length === 0\n ? `no ${record._tag} record at ${record.name}`\n : `expected ${DnsRecord.data(record)}; found ${values.join(\", \")}`;\n return { status, values, detail };\n};\n\nconst combine = (statuses: ReadonlyArray<Storage.RequirementStatus>): Storage.RequirementStatus => {\n const known = statuses.filter((status) => status !== \"unknown\");\n if (known.length === 0) return \"unknown\";\n if (known.includes(\"mismatch\")) return \"mismatch\";\n return known.every((status) => status === \"satisfied\") ? \"satisfied\" : \"missing\";\n};\n\nconst quorumStatus = (\n quorum: PolicyShape[\"quorum\"],\n statuses: ReadonlyArray<Storage.RequirementStatus>,\n): Storage.RequirementStatus => {\n const known = statuses.filter((status) => status !== \"unknown\");\n const satisfied = known.filter((status) => status === \"satisfied\").length;\n const needed =\n quorum === \"any\" ? 1 : quorum === \"all\" ? Math.max(1, known.length) : quorum.minimum;\n if (known.includes(\"mismatch\")) return \"mismatch\";\n if (known.length > 0 && satisfied >= needed) return \"satisfied\";\n return known.length === 0 ? \"unknown\" : \"missing\";\n};\n\nconst overallOf = (\n requirements: ReadonlyArray<{ readonly status: Storage.RequirementStatus }>,\n host: ReadonlyArray<HostEvidence>,\n): Storage.Overall => {\n if (\n requirements.some(({ status }) => status === \"mismatch\") ||\n host.some(({ status }) => status === \"failed\")\n ) {\n return \"failed\";\n }\n const ready =\n requirements.length + host.length > 0 &&\n requirements.every(({ status }) => status === \"satisfied\") &&\n host.every(({ status }) => status === \"ok\");\n return ready ? \"ready\" : \"pending\";\n};\n\n/** The identity of a requirement set: type, name, data, policy, and constraint, independent of labels and order. */\nconst requirementSetKey = (requirements: ReadonlyArray<{ readonly record: DnsRecord.Model }>) =>\n requirements\n .map(\n ({ record }) =>\n `${record._tag} ${record.name} ${DnsRecord.data(record)} ${record.policy} ${record._tag === \"TXT\" ? (record.constraint ?? \"\") : \"\"}`,\n )\n .sort()\n .join(\"\\n\");\n\nexport const make: Effect.Effect<\n Interface,\n never,\n Storage.Service | Connect.Service | Resolver.Service\n> = Effect.gen(function* () {\n const storage = yield* Storage.Service;\n const connect = yield* Connect.Service;\n const resolver = yield* Resolver.Service;\n\n const attachmentFor = (\n input: string,\n ): Fx<{\n readonly domain: DomainName.Model;\n readonly attachment: Storage.Attachment | null;\n }> =>\n Effect.gen(function* () {\n const domain = yield* DomainName.decode(input);\n const attachment = yield* storage.attachments.byDomain(domain);\n return { domain, attachment: Option.getOrNull(attachment) };\n });\n\n const decodeRow = (row: Storage.Readiness): Effect.Effect<Readiness, Errors.DomainKitError> =>\n Effect.gen(function* () {\n const requirements = yield* Effect.forEach(row.requirements, (requirement) =>\n Errors.decode(StoredEvidence, requirement.evidence, \"evidence\").pipe(\n Effect.map((evidence): Requirement => ({\n key: requirementKey(requirement.record),\n operationId: requirement.operationId,\n record: requirement.record,\n status: requirement.status,\n evidence,\n })),\n ),\n );\n const host = yield* Errors.decode(StoredHost, row.host, \"host\");\n return {\n domain: row.domain,\n attachmentId: row.attachmentId,\n overall: row.overall,\n requirements,\n host,\n checkedAt: row.checkedAt,\n nextCheckAt: row.nextCheckAt,\n };\n });\n\n /** Persist readiness, carrying the pending streak forward for the backoff ladder. */\n const store = (input: {\n readonly domain: string;\n readonly attachment: Storage.Attachment | null;\n readonly requirements: ReadonlyArray<Requirement>;\n readonly host: ReadonlyArray<HostEvidence>;\n readonly previous: Option.Option<Storage.Readiness>;\n readonly cause: Cause;\n }): Fx<Readiness> =>\n Effect.gen(function* () {\n const principal = yield* Principal.Service;\n const policy = yield* Policy;\n const observer = yield* Observer;\n const now = yield* DateTime.now;\n const overall = overallOf(input.requirements, input.host);\n const sameRequirements =\n Option.isSome(input.previous) &&\n requirementSetKey(input.previous.value.requirements) ===\n requirementSetKey(input.requirements);\n const pendingSince =\n overall === \"ready\"\n ? null\n : sameRequirements &&\n Option.isSome(input.previous) &&\n input.previous.value.pendingSince !== null\n ? input.previous.value.pendingSince\n : now;\n const nextCheckAt =\n pendingSince === null\n ? null\n : DateTime.addDuration(\n now,\n Duration.millis(\n policy.backoff(DateTime.toEpochMillis(now) - DateTime.toEpochMillis(pendingSince)),\n ),\n );\n const row = new Storage.Readiness({\n domain: input.domain,\n attachmentId: input.attachment?.id ?? null,\n ownerId: principal.ownerId,\n overall,\n requirements: input.requirements.map((requirement) => ({\n operationId: requirement.operationId,\n record: requirement.record,\n status: requirement.status,\n evidence: Schema.encodeSync(StoredEvidence)(requirement.evidence),\n })),\n host: Schema.encodeSync(StoredHost)(input.host),\n pendingSince,\n checkedAt: now,\n nextCheckAt,\n });\n yield* storage.readiness.put(row);\n const readiness: Readiness = {\n domain: row.domain,\n attachmentId: row.attachmentId,\n overall,\n requirements: input.requirements,\n host: input.host,\n checkedAt: now,\n nextCheckAt,\n };\n yield* observer\n .readinessChanged({\n ownerId: principal.ownerId,\n domain: row.domain,\n readiness,\n cause: input.cause,\n })\n .pipe(\n Effect.catchCause((cause) =>\n Effect.logWarning(`Verify.Observer failed for ${row.domain}`, cause),\n ),\n );\n return readiness;\n });\n\n const defaultRequirements = (\n attachment: Storage.Attachment,\n ): Fx<\n ReadonlyArray<{\n readonly operationId: Plan.OperationId | null;\n readonly record: DnsRecord.Model;\n }>\n > =>\n Effect.gen(function* () {\n const latest = yield* storage.attempts.latest(attachment.id, \"provisioning\");\n if (Option.isNone(latest) || latest.value.receipt === null) {\n return yield* Errors.fail(\n new Reason.InvalidInput({\n message: `${attachment.domain} has no provisioning receipt; pass requirements to observe`,\n field: \"requirements\",\n }),\n );\n }\n const receipt = latest.value.receipt;\n const inPlace = new Set(\n receipt.outcomes.flatMap((outcome) =>\n outcome._tag === \"Applied\" || (outcome._tag === \"Skipped\" && outcome.reason === \"noop\")\n ? [outcome.operationId]\n : [],\n ),\n );\n return latest.value.plan.operations\n .filter((operation) => inPlace.has(operation.id))\n .map((operation) => ({ operationId: operation.id, record: operation.record }));\n });\n\n const observe: Interface[\"observe\"] = (input) =>\n Effect.gen(function* () {\n const policy = yield* Policy;\n const { domain, attachment } = yield* attachmentFor(input.domain);\n if (input.requirements === undefined && attachment === null) {\n return yield* Errors.fail(\n new Reason.InvalidInput({\n message: `${domain} is not attached; pass requirements to observe public DNS`,\n field: \"requirements\",\n }),\n );\n }\n const requirements =\n input.requirements === undefined && attachment !== null\n ? yield* defaultRequirements(attachment)\n : (input.requirements ?? []).map((record) => ({ operationId: null, record }));\n if (requirements.length === 0) {\n return yield* Errors.fail(\n new Reason.InvalidInput({\n message: \"Nothing to observe: pass at least one requirement\",\n field: \"requirements\",\n }),\n );\n }\n const now = yield* DateTime.now;\n // Provider readback is evidence when the attachment's session can be built; when it\n // cannot (no attachment, revoked credential, provider outage) public DNS stands alone.\n const providerSide = yield* attachment === null\n ? Effect.succeed(Option.none<ProviderSide>())\n : Effect.gen(function* () {\n const connection = yield* storage.connections.get(attachment.connectionId);\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const { session, target } = yield* connect.session(attachment.id);\n const observed = yield* session.dns(target).list(target.zone);\n return Option.some<ProviderSide>({\n provider: authorization.provider,\n records: observed.map(({ record }) => record),\n });\n }).pipe(Effect.catch(() => Effect.succeed(Option.none<ProviderSide>())));\n const observed = yield* Effect.forEach(\n requirements,\n ({ operationId, record }) =>\n Effect.gen(function* () {\n const providerEvidence = Option.isSome(providerSide)\n ? [\n new ProviderEvidence({\n provider: providerSide.value.provider,\n ...observation(record, providerSide.value.records),\n observedAt: now,\n }),\n ]\n : [];\n const outcomes = yield* resolver.resolve(record.name, record._tag);\n const publicEvidence = outcomes.map((outcome) =>\n outcome._tag === \"Answered\"\n ? new PublicDnsEvidence({\n resolver: outcome.answer.resolver,\n ...observation(record, outcome.answer.records),\n observedAt: now,\n })\n : new PublicDnsEvidence({\n resolver: outcome.resolver,\n status: \"unknown\",\n values: [],\n detail:\n outcome._tag === \"TimedOut\"\n ? `${outcome.resolver} timed out`\n : outcome.message,\n observedAt: now,\n }),\n );\n const publicStatus = quorumStatus(\n policy.quorum,\n publicEvidence.map(({ status }) => status),\n );\n const status = combine([\n ...providerEvidence.map((evidence) => evidence.status),\n publicStatus,\n ]);\n return {\n key: requirementKey(record),\n operationId,\n record,\n status,\n evidence: [...providerEvidence, ...publicEvidence],\n } satisfies Requirement;\n }),\n { concurrency: \"unbounded\" },\n );\n const previous = yield* storage.readiness.get(domain);\n const host = Option.isSome(previous)\n ? yield* Errors.decode(StoredHost, previous.value.host, \"host\")\n : [];\n return yield* store({\n domain,\n attachment,\n requirements: observed,\n host,\n previous,\n cause: \"observe\",\n });\n });\n\n const attachEvidence: Interface[\"attachEvidence\"] = (input) =>\n Effect.gen(function* () {\n const { domain, attachment } = yield* attachmentFor(input.domain);\n const previous = yield* storage.readiness.get(domain);\n const current = Option.isSome(previous)\n ? yield* decodeRow(previous.value)\n : {\n requirements: [] as ReadonlyArray<Requirement>,\n host: [] as ReadonlyArray<HostEvidence>,\n };\n const bySource = new Map(current.host.map((evidence) => [evidence.source, evidence]));\n for (const evidence of input.evidence) bySource.set(evidence.source, evidence);\n return yield* store({\n domain,\n attachment,\n requirements: current.requirements,\n host: [...bySource.values()],\n previous,\n cause: \"evidence\",\n });\n });\n\n const latest: Interface[\"latest\"] = (domain) =>\n Effect.gen(function* () {\n const { domain: name } = yield* attachmentFor(domain);\n const stored = yield* storage.readiness.get(name);\n return Option.isSome(stored) ? yield* decodeRow(stored.value) : null;\n });\n\n return { observe, attachEvidence, latest };\n});\n\nexport const layer: Layer.Layer<\n Service,\n never,\n Storage.Service | Connect.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 observe = accessor((service) => service.observe);\nexport const attachEvidence = accessor((service) => service.attachEvidence);\nexport const latest = accessor((service) => service.latest);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Verify.mjs","names":["Storage.RequirementStatus","DnsRecord.data","DnsRecord.equals","DnsRecord.sameSet","Storage.Service","Connect.Service","Resolver.Service","DomainName.decode","Errors.decode","Principal.Service","Storage.Readiness","Errors.fail","Reason.InvalidInput"],"sources":["../src/Verify.ts"],"sourcesContent":["/**\n * Observe provider and public DNS for an attachment, persist per-requirement readiness, and say\n * when to look again. The host keeps its own state machine (and any provider-side verification\n * such as SES) and feeds that in as `HostEvidence`.\n */\nimport { Context, DateTime, Duration, Effect, Layer, Option, Schema } from \"effect\";\n\nimport * as Connect from \"./Connect.ts\";\nimport * as DnsRecord from \"./DnsRecord.ts\";\nimport * as Errors from \"./internal/error.ts\";\nimport { collision } from \"./internal/reconciliation.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as DomainName from \"./DomainName.ts\";\nimport type * as Plan from \"./Plan.ts\";\nimport * as Principal from \"./Principal.ts\";\nimport * as Resolver from \"./Resolver.ts\";\nimport * as Storage from \"./Storage.ts\";\n\n/**\n * Every piece of evidence carries `values`, what the observer returned for the requirement's name\n * and type (empty when nothing was found, the lookup timed out, or the resolver failed), and\n * `detail`, `null` when the requirement is satisfied and otherwise the mismatch summary or the\n * resolver's error text, so a host can render \"found X, expected Y\".\n */\n// Rows written before these fields existed decode with the empty values.\nconst Values = Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed([])));\nconst Detail = Schema.NullOr(Schema.String).pipe(\n Schema.withDecodingDefaultKey(Effect.succeed(null)),\n);\n\nexport class ProviderEvidence extends Schema.TaggedClass<ProviderEvidence>(\n \"@domainkit/Evidence/Provider\",\n)(\"Provider\", {\n provider: Schema.String,\n status: Storage.RequirementStatus,\n values: Values,\n detail: Detail,\n observedAt: Schema.DateTimeUtcFromString,\n}) {}\nexport class PublicDnsEvidence extends Schema.TaggedClass<PublicDnsEvidence>(\n \"@domainkit/Evidence/PublicDns\",\n)(\"PublicDns\", {\n resolver: Schema.String,\n status: Storage.RequirementStatus,\n values: Values,\n detail: Detail,\n observedAt: Schema.DateTimeUtcFromString,\n}) {}\n/** Anything the host knows that DomainKit cannot observe: SES identity status, a CDN cert, ... */\nexport class HostEvidence extends Schema.TaggedClass<HostEvidence>(\"@domainkit/Evidence/Host\")(\n \"Host\",\n {\n source: Schema.String,\n status: Schema.Literals([\"ok\", \"pending\", \"failed\"]),\n label: Schema.String,\n detail: Detail,\n observedAt: Schema.DateTimeUtcFromString,\n },\n) {}\nexport const Evidence = Schema.Union([ProviderEvidence, PublicDnsEvidence, HostEvidence]);\nexport type Evidence = typeof Evidence.Type;\n\nexport interface Requirement {\n /** `requirementKey(record)`: what a host pairs its own row against, by content. */\n readonly key: string;\n readonly operationId: Plan.OperationId | null;\n readonly record: DnsRecord.Model;\n readonly status: Storage.RequirementStatus;\n readonly evidence: ReadonlyArray<Evidence>;\n}\n\nexport interface Readiness {\n readonly domain: string;\n /** `null` for observe-only domains that have no attachment. */\n readonly attachmentId: string | null;\n readonly overall: Storage.Overall;\n readonly requirements: ReadonlyArray<Requirement>;\n readonly host: ReadonlyArray<HostEvidence>;\n readonly checkedAt: DateTime.Utc;\n readonly nextCheckAt: DateTime.Utc | null;\n}\n\ntype Fx<A> = Effect.Effect<A, Errors.DomainKitError, Principal.Service>;\n\nexport interface Interface {\n /**\n * What to observe, in order of precedence: `requirements` when supplied (an attached domain\n * without a provisioning receipt, or a domain with no attachment whose records the customer\n * applies by hand), else the latest provisioning receipt of the domain's attachment (records it\n * applied or found in place). Neither is `InvalidInput`. Provider evidence is added when the\n * attachment's session can be built; public DNS is always observed, and readiness is stored\n * per domain either way.\n */\n readonly observe: (input: {\n readonly domain: string;\n readonly requirements?: ReadonlyArray<DnsRecord.Model>;\n }) => Fx<Readiness>;\n /**\n * Merge host evidence into stored readiness without re-observing DNS. Before the first `observe`\n * the readiness has no requirements, so it is never `ready`: `failed` when a host signal failed,\n * otherwise `pending` with `nextCheckAt` set for the first observation.\n */\n readonly attachEvidence: (input: {\n readonly domain: string;\n readonly evidence: ReadonlyArray<HostEvidence>;\n }) => Fx<Readiness>;\n readonly latest: (domain: string) => Fx<Readiness | null>;\n}\n\nexport class Service extends Context.Service<Service, Interface>()(\"@domainkit/Verify\") {}\n\n/**\n * A requirement's identity: the record's type, name, and data, and nothing else. It is what a host\n * pairs its own rows against readiness by, instead of trusting the order of the array.\n * `@domainkit/react`'s `Records.identity` is this function.\n */\nexport const requirementKey = (record: DnsRecord.Model): string =>\n [record._tag, record.name, DnsRecord.data(record)].join(\":\");\n\n/** Counts across a readiness's requirements. `observed` is false when there is no readiness yet. */\nexport interface Summary {\n readonly observed: boolean;\n readonly total: number;\n readonly satisfied: number;\n readonly missing: number;\n readonly mismatch: number;\n readonly unknown: number;\n}\n\n/**\n * How a readiness stands, as counts. Pure and total: a host renders \"3 of 4 found\" from the stored\n * fact without deciding what an absent readiness means, because `observed` says so. The parameter\n * is the least a value has to carry to be summarised, so core `Readiness` and the wire shape both\n * pass without naming a type between them.\n */\nexport const summary = (\n readiness: {\n readonly requirements: ReadonlyArray<{ readonly status: Storage.RequirementStatus }>;\n } | null,\n): Summary => {\n const statuses = readiness?.requirements.map(({ status }) => status) ?? [];\n const count = (status: Storage.RequirementStatus): number =>\n statuses.filter((candidate) => candidate === status).length;\n return {\n observed: readiness !== null,\n total: statuses.length,\n satisfied: count(\"satisfied\"),\n missing: count(\"missing\"),\n mismatch: count(\"mismatch\"),\n unknown: count(\"unknown\"),\n };\n};\n\nexport interface PolicyShape {\n /** Delay before the next check, given time since the first pending observation. Default ladder: 15s, 1m, 5m, 30m. */\n readonly backoff: (pendingForMs: number) => number;\n /** Which resolvers must agree for `satisfied`: default `any`. */\n readonly quorum: \"any\" | \"all\" | { readonly minimum: number };\n}\nexport const defaults: PolicyShape = {\n backoff: (pendingForMs) =>\n pendingForMs < 60_000\n ? 15_000\n : pendingForMs < 10 * 60_000\n ? 60_000\n : pendingForMs < 60 * 60_000\n ? 5 * 60_000\n : 30 * 60_000,\n quorum: \"any\",\n};\nexport class Policy extends Context.Reference<PolicyShape>(\"@domainkit/Verify/Policy\", {\n defaultValue: () => defaults,\n}) {}\n\n/** Why readiness was written: a DNS observation, or host evidence merged into the stored row. */\nexport type Cause = \"observe\" | \"evidence\";\n\nexport interface ReadinessChanged {\n /** The tenant the row was written under, from the `Principal` the write ran as. */\n readonly ownerId: string;\n readonly domain: string;\n readonly readiness: Readiness;\n readonly cause: Cause;\n}\n\nexport interface ObserverShape {\n /**\n * Called once per stored readiness, after the write. A host that projects readiness onto its own\n * rows, wakes a durable job at `nextCheckAt`, or notifies a customer hangs it here instead of\n * mirroring the fact at every call site.\n *\n * Readiness is stored per owner and domain, and an observer provided over `DomainKit.layer` is\n * scoped to the layer rather than to one request, so a host that serves more than one tenant\n * keys its projection by `ownerId` and `domain`: the same domain name can be attached in two\n * tenants at once.\n *\n * Events for one domain are not ordered against each other: two observations that overlap both\n * store, last write wins in `Storage`, and their callbacks can finish in either order. A\n * projection compares `readiness.checkedAt`, which is the moment the row was written, and keeps\n * the later one; `latest` remains the authority.\n */\n readonly readinessChanged: (event: ReadinessChanged) => Effect.Effect<void, unknown>;\n}\n\n/**\n * The host seam for \"DomainKit wrote readiness\". The default does nothing; provide one with\n * `Effect.provideService(Verify.Observer, ...)` or `Layer.succeed(Verify.Observer, ...)` over\n * `DomainKit.layer`, the same way `Policy` is overridden.\n *\n * It runs after `storage.readiness.put` returns, outside any transaction, because `Storage`\n * exposes none. A failure or defect in the observer is logged and swallowed: the observation is\n * already durable, and losing it because a projection failed would make the stored fact depend on\n * the host's side effects. A host that needs the projection to be reliable enqueues durable work\n * here rather than doing the work inline.\n */\nexport class Observer extends Context.Reference<ObserverShape>(\"@domainkit/Verify/Observer\", {\n defaultValue: (): ObserverShape => ({ readinessChanged: () => Effect.void }),\n}) {}\n\n// ---------------------------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------------------------\n\nconst StoredEvidence = Schema.Array(Evidence);\n\ninterface ProviderSide {\n readonly provider: string;\n readonly records: ReadonlyArray<DnsRecord.Observed>;\n}\nconst StoredHost = Schema.Array(HostEvidence);\n\n/** A requirement is satisfied only when its exact match also meets its safety constraints. */\nexport const statusAgainst = (\n record: DnsRecord.Model,\n observed: ReadonlyArray<DnsRecord.Observed>,\n): Storage.RequirementStatus => {\n if (collision(record, observed) !== undefined) return \"mismatch\";\n return observed.some((candidate) => DnsRecord.equals(candidate, record))\n ? \"satisfied\"\n : \"missing\";\n};\n\ninterface Observation {\n readonly status: Storage.RequirementStatus;\n readonly values: ReadonlyArray<string>;\n readonly detail: string | null;\n}\n\nconst render = (observed: DnsRecord.Observed): string =>\n observed._tag === \"Opaque\" ? JSON.stringify(observed.raw) : DnsRecord.data(observed);\n\n/** Status plus what the observer holds for the requirement's name and type. */\nconst observation = (\n record: DnsRecord.Model,\n observed: ReadonlyArray<DnsRecord.Observed>,\n): Observation => {\n const status = statusAgainst(record, observed);\n const values = observed.filter((candidate) => DnsRecord.sameSet(candidate, record)).map(render);\n const detail =\n status === \"satisfied\"\n ? null\n : values.length === 0\n ? `no ${record._tag} record at ${record.name}`\n : `expected ${DnsRecord.data(record)}; found ${values.join(\", \")}`;\n return { status, values, detail };\n};\n\nconst combine = (statuses: ReadonlyArray<Storage.RequirementStatus>): Storage.RequirementStatus => {\n const known = statuses.filter((status) => status !== \"unknown\");\n if (known.length === 0) return \"unknown\";\n if (known.includes(\"mismatch\")) return \"mismatch\";\n return known.every((status) => status === \"satisfied\") ? \"satisfied\" : \"missing\";\n};\n\nconst quorumStatus = (\n quorum: PolicyShape[\"quorum\"],\n statuses: ReadonlyArray<Storage.RequirementStatus>,\n): Storage.RequirementStatus => {\n const known = statuses.filter((status) => status !== \"unknown\");\n const satisfied = known.filter((status) => status === \"satisfied\").length;\n const needed =\n quorum === \"any\" ? 1 : quorum === \"all\" ? Math.max(1, known.length) : quorum.minimum;\n if (known.includes(\"mismatch\")) return \"mismatch\";\n if (known.length > 0 && satisfied >= needed) return \"satisfied\";\n return known.length === 0 ? \"unknown\" : \"missing\";\n};\n\nconst overallOf = (\n requirements: ReadonlyArray<{ readonly status: Storage.RequirementStatus }>,\n host: ReadonlyArray<HostEvidence>,\n): Storage.Overall => {\n if (\n requirements.some(({ status }) => status === \"mismatch\") ||\n host.some(({ status }) => status === \"failed\")\n ) {\n return \"failed\";\n }\n // Readiness is a DNS fact: host evidence can hold a domain back but never makes it ready alone,\n // so a row with no observed requirement stays pending and keeps its next check scheduled.\n const ready =\n requirements.length > 0 &&\n requirements.every(({ status }) => status === \"satisfied\") &&\n host.every(({ status }) => status === \"ok\");\n return ready ? \"ready\" : \"pending\";\n};\n\n/** The identity of a requirement set: type, name, data, policy, and constraint, independent of labels and order. */\nconst requirementSetKey = (requirements: ReadonlyArray<{ readonly record: DnsRecord.Model }>) =>\n requirements\n .map(\n ({ record }) =>\n `${record._tag} ${record.name} ${DnsRecord.data(record)} ${record.policy} ${record._tag === \"TXT\" ? (record.constraint ?? \"\") : \"\"}`,\n )\n .sort()\n .join(\"\\n\");\n\nexport const make: Effect.Effect<\n Interface,\n never,\n Storage.Service | Connect.Service | Resolver.Service\n> = Effect.gen(function* () {\n const storage = yield* Storage.Service;\n const connect = yield* Connect.Service;\n const resolver = yield* Resolver.Service;\n\n const attachmentFor = (\n input: string,\n ): Fx<{\n readonly domain: DomainName.Model;\n readonly attachment: Storage.Attachment | null;\n }> =>\n Effect.gen(function* () {\n const domain = yield* DomainName.decode(input);\n const attachment = yield* storage.attachments.byDomain(domain);\n return { domain, attachment: Option.getOrNull(attachment) };\n });\n\n const decodeRow = (row: Storage.Readiness): Effect.Effect<Readiness, Errors.DomainKitError> =>\n Effect.gen(function* () {\n const requirements = yield* Effect.forEach(row.requirements, (requirement) =>\n Errors.decode(StoredEvidence, requirement.evidence, \"evidence\").pipe(\n Effect.map((evidence): Requirement => ({\n key: requirementKey(requirement.record),\n operationId: requirement.operationId,\n record: requirement.record,\n status: requirement.status,\n evidence,\n })),\n ),\n );\n const host = yield* Errors.decode(StoredHost, row.host, \"host\");\n // Overall is derived, not trusted: a row stored under an older rule (host evidence alone\n // read as ready) reads back as pending, due at once, so a worker following `nextCheckAt`\n // observes it.\n const overall = overallOf(requirements, host);\n return {\n domain: row.domain,\n attachmentId: row.attachmentId,\n overall,\n requirements,\n host,\n checkedAt: row.checkedAt,\n nextCheckAt: row.nextCheckAt ?? (overall === \"ready\" ? null : row.checkedAt),\n };\n });\n\n /** Persist readiness, carrying the pending streak forward for the backoff ladder. */\n const store = (input: {\n readonly domain: string;\n readonly attachment: Storage.Attachment | null;\n readonly requirements: ReadonlyArray<Requirement>;\n readonly host: ReadonlyArray<HostEvidence>;\n readonly previous: Option.Option<Storage.Readiness>;\n readonly cause: Cause;\n }): Fx<Readiness> =>\n Effect.gen(function* () {\n const principal = yield* Principal.Service;\n const policy = yield* Policy;\n const observer = yield* Observer;\n const now = yield* DateTime.now;\n const overall = overallOf(input.requirements, input.host);\n const sameRequirements =\n Option.isSome(input.previous) &&\n requirementSetKey(input.previous.value.requirements) ===\n requirementSetKey(input.requirements);\n const pendingSince =\n overall === \"ready\"\n ? null\n : sameRequirements &&\n Option.isSome(input.previous) &&\n input.previous.value.pendingSince !== null\n ? input.previous.value.pendingSince\n : now;\n const nextCheckAt =\n pendingSince === null\n ? null\n : DateTime.addDuration(\n now,\n Duration.millis(\n policy.backoff(DateTime.toEpochMillis(now) - DateTime.toEpochMillis(pendingSince)),\n ),\n );\n const row = new Storage.Readiness({\n domain: input.domain,\n attachmentId: input.attachment?.id ?? null,\n ownerId: principal.ownerId,\n overall,\n requirements: input.requirements.map((requirement) => ({\n operationId: requirement.operationId,\n record: requirement.record,\n status: requirement.status,\n evidence: Schema.encodeSync(StoredEvidence)(requirement.evidence),\n })),\n host: Schema.encodeSync(StoredHost)(input.host),\n pendingSince,\n checkedAt: now,\n nextCheckAt,\n });\n yield* storage.readiness.put(row);\n const readiness: Readiness = {\n domain: row.domain,\n attachmentId: row.attachmentId,\n overall,\n requirements: input.requirements,\n host: input.host,\n checkedAt: now,\n nextCheckAt,\n };\n yield* observer\n .readinessChanged({\n ownerId: principal.ownerId,\n domain: row.domain,\n readiness,\n cause: input.cause,\n })\n .pipe(\n Effect.catchCause((cause) =>\n Effect.logWarning(`Verify.Observer failed for ${row.domain}`, cause),\n ),\n );\n return readiness;\n });\n\n const defaultRequirements = (\n attachment: Storage.Attachment,\n ): Fx<\n ReadonlyArray<{\n readonly operationId: Plan.OperationId | null;\n readonly record: DnsRecord.Model;\n }>\n > =>\n Effect.gen(function* () {\n const latest = yield* storage.attempts.latest(attachment.id, \"provisioning\");\n if (Option.isNone(latest) || latest.value.receipt === null) {\n return yield* Errors.fail(\n new Reason.InvalidInput({\n message: `${attachment.domain} has no provisioning receipt; pass requirements to observe`,\n field: \"requirements\",\n }),\n );\n }\n const receipt = latest.value.receipt;\n const inPlace = new Set(\n receipt.outcomes.flatMap((outcome) =>\n outcome._tag === \"Applied\" || (outcome._tag === \"Skipped\" && outcome.reason === \"noop\")\n ? [outcome.operationId]\n : [],\n ),\n );\n return latest.value.plan.operations\n .filter((operation) => inPlace.has(operation.id))\n .map((operation) => ({ operationId: operation.id, record: operation.record }));\n });\n\n const observe: Interface[\"observe\"] = (input) =>\n Effect.gen(function* () {\n const policy = yield* Policy;\n const { domain, attachment } = yield* attachmentFor(input.domain);\n if (input.requirements === undefined && attachment === null) {\n return yield* Errors.fail(\n new Reason.InvalidInput({\n message: `${domain} is not attached; pass requirements to observe public DNS`,\n field: \"requirements\",\n }),\n );\n }\n const requirements =\n input.requirements === undefined && attachment !== null\n ? yield* defaultRequirements(attachment)\n : (input.requirements ?? []).map((record) => ({ operationId: null, record }));\n if (requirements.length === 0) {\n return yield* Errors.fail(\n new Reason.InvalidInput({\n message: \"Nothing to observe: pass at least one requirement\",\n field: \"requirements\",\n }),\n );\n }\n const now = yield* DateTime.now;\n // Provider readback is evidence when the attachment's session can be built; when it\n // cannot (no attachment, revoked credential, provider outage) public DNS stands alone.\n const providerSide = yield* attachment === null\n ? Effect.succeed(Option.none<ProviderSide>())\n : Effect.gen(function* () {\n const connection = yield* storage.connections.get(attachment.connectionId);\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const { session, target } = yield* connect.session(attachment.id);\n const observed = yield* session.dns(target).list(target.zone);\n return Option.some<ProviderSide>({\n provider: authorization.provider,\n records: observed.map(({ record }) => record),\n });\n }).pipe(Effect.catch(() => Effect.succeed(Option.none<ProviderSide>())));\n const observed = yield* Effect.forEach(\n requirements,\n ({ operationId, record }) =>\n Effect.gen(function* () {\n const providerEvidence = Option.isSome(providerSide)\n ? [\n new ProviderEvidence({\n provider: providerSide.value.provider,\n ...observation(record, providerSide.value.records),\n observedAt: now,\n }),\n ]\n : [];\n const outcomes = yield* resolver.resolve(record.name, record._tag);\n const publicEvidence = outcomes.map((outcome) =>\n outcome._tag === \"Answered\"\n ? new PublicDnsEvidence({\n resolver: outcome.answer.resolver,\n ...observation(record, outcome.answer.records),\n observedAt: now,\n })\n : new PublicDnsEvidence({\n resolver: outcome.resolver,\n status: \"unknown\",\n values: [],\n detail:\n outcome._tag === \"TimedOut\"\n ? `${outcome.resolver} timed out`\n : outcome.message,\n observedAt: now,\n }),\n );\n const publicStatus = quorumStatus(\n policy.quorum,\n publicEvidence.map(({ status }) => status),\n );\n const status = combine([\n ...providerEvidence.map((evidence) => evidence.status),\n publicStatus,\n ]);\n return {\n key: requirementKey(record),\n operationId,\n record,\n status,\n evidence: [...providerEvidence, ...publicEvidence],\n } satisfies Requirement;\n }),\n { concurrency: \"unbounded\" },\n );\n const previous = yield* storage.readiness.get(domain);\n const host = Option.isSome(previous)\n ? yield* Errors.decode(StoredHost, previous.value.host, \"host\")\n : [];\n return yield* store({\n domain,\n attachment,\n requirements: observed,\n host,\n previous,\n cause: \"observe\",\n });\n });\n\n const attachEvidence: Interface[\"attachEvidence\"] = (input) =>\n Effect.gen(function* () {\n const { domain, attachment } = yield* attachmentFor(input.domain);\n const previous = yield* storage.readiness.get(domain);\n const current = Option.isSome(previous)\n ? yield* decodeRow(previous.value)\n : {\n requirements: [] as ReadonlyArray<Requirement>,\n host: [] as ReadonlyArray<HostEvidence>,\n };\n const bySource = new Map(current.host.map((evidence) => [evidence.source, evidence]));\n for (const evidence of input.evidence) bySource.set(evidence.source, evidence);\n return yield* store({\n domain,\n attachment,\n requirements: current.requirements,\n host: [...bySource.values()],\n previous,\n cause: \"evidence\",\n });\n });\n\n const latest: Interface[\"latest\"] = (domain) =>\n Effect.gen(function* () {\n const { domain: name } = yield* attachmentFor(domain);\n const stored = yield* storage.readiness.get(name);\n return Option.isSome(stored) ? yield* decodeRow(stored.value) : null;\n });\n\n return { observe, attachEvidence, latest };\n});\n\nexport const layer: Layer.Layer<\n Service,\n never,\n Storage.Service | Connect.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 observe = accessor((service) => service.observe);\nexport const attachEvidence = accessor((service) => service.attachEvidence);\nexport const latest = accessor((service) => service.latest);\n"],"mappings":"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"}
|
package/dist/entry/client.mjs
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { Transport_exports as Transport };
|
|
1
|
+
export { Transport_exports as Transport } from "../Transport.mjs";
|
package/dist/entry/server.mjs
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { Server_exports as Server };
|
|
1
|
+
export { Server_exports as Server } from "../Server.mjs";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { InvalidInput } from "../Reason.mjs";
|
|
2
|
+
import { fail } from "./error.mjs";
|
|
3
|
+
//#region src/internal/callback.ts
|
|
4
|
+
/** A leaked flow id must reveal neither existence nor ownership, including read races. */
|
|
5
|
+
const refuse = fail(new InvalidInput({
|
|
6
|
+
message: "This callback does not match a connection you started",
|
|
7
|
+
field: "state"
|
|
8
|
+
}));
|
|
9
|
+
const missing = (error) => error.reason._tag === "NotFound" || error.reason._tag === "Expired";
|
|
10
|
+
//#endregion
|
|
11
|
+
export { missing, refuse };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=callback.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callback.mjs","names":["Errors.fail","Reason.InvalidInput"],"sources":["../../src/internal/callback.ts"],"sourcesContent":["import * as Errors from \"./error.ts\";\nimport * as Reason from \"../Reason.ts\";\n\n/** A leaked flow id must reveal neither existence nor ownership, including read races. */\nexport const refuse = Errors.fail(\n new Reason.InvalidInput({\n message: \"This callback does not match a connection you started\",\n field: \"state\",\n }),\n);\nexport const missing = (error: Errors.DomainKitError): boolean =>\n error.reason._tag === \"NotFound\" || error.reason._tag === \"Expired\";\n"],"mappings":";;;;AAIA,MAAa,SAASA,KACpB,IAAIC,aAAoB;CACtB,SAAS;CACT,OAAO;AACT,CAAC,CACH;AACA,MAAa,WAAW,UACtB,MAAM,OAAO,SAAS,cAAc,MAAM,OAAO,SAAS"}
|
package/dist/internal/config.mjs
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
import { InvalidInput } from "../Reason.mjs";
|
|
2
|
-
import { DomainKitError } from "./error.mjs";
|
|
3
|
-
import { Config, Effect } from "effect";
|
|
2
|
+
import { DomainKitError, fail } from "./error.mjs";
|
|
3
|
+
import { Config, Effect, Redacted } from "effect";
|
|
4
4
|
//#region src/internal/config.ts
|
|
5
5
|
/** Accept a literal or a `Config`; config failures surface as `InvalidInput` on `field`. */
|
|
6
|
-
const resolve = (value, field) => Config.isConfig(value) ? value.pipe(Effect.mapError((
|
|
7
|
-
message:
|
|
6
|
+
const resolve = (value, field) => Config.isConfig(value) ? value.pipe(Effect.mapError(() => new DomainKitError({ reason: new InvalidInput({
|
|
7
|
+
message: `Could not resolve ${field}`,
|
|
8
8
|
field
|
|
9
9
|
}) }))) : Effect.succeed(value);
|
|
10
|
+
/** Configuration diagnostics name the field, never the rejected value. */
|
|
11
|
+
const requireValue = (value, valid, field, message) => Effect.suspend(() => valid(value) ? Effect.succeed(value) : fail(new InvalidInput({
|
|
12
|
+
field,
|
|
13
|
+
message
|
|
14
|
+
})));
|
|
15
|
+
const nonempty = (value, field) => requireValue(value, (v) => typeof v === "string" && v.trim().length > 0, field, `${field} must be a nonempty string`);
|
|
16
|
+
const secret = (value, field) => requireValue(value, (v) => Redacted.isRedacted(v) && typeof Redacted.value(v) === "string" && Redacted.value(v).trim().length > 0, field, `${field} must be a nonempty Redacted string`);
|
|
17
|
+
/** Paths and split emulator origins are supported; URL suffixes must remain paths. */
|
|
18
|
+
const endpoint = (value, field) => requireValue(value, (v) => {
|
|
19
|
+
if (typeof v !== "string" || v !== v.trim() || [...v].some((character) => character.charCodeAt(0) < 32)) return false;
|
|
20
|
+
const url = URL.parse(v);
|
|
21
|
+
return url !== null && (url.protocol === "https:" || url.protocol === "http:") && url.username === "" && url.password === "" && !url.href.includes("?") && !url.href.includes("#");
|
|
22
|
+
}, field, `${field} must be an HTTP(S) URL without surrounding whitespace, control characters, credentials, query, or fragment`);
|
|
23
|
+
const scopes = (value, field) => requireValue(value, (v) => Array.isArray(v) && v.every((scope) => typeof scope === "string" && /^[\x21\x23-\x5B\x5D-\x7E]+$/.test(scope)), field, `${field} must contain valid OAuth scope tokens`);
|
|
10
24
|
//#endregion
|
|
11
|
-
export { resolve };
|
|
25
|
+
export { endpoint, nonempty, requireValue, resolve, scopes, secret };
|
|
12
26
|
|
|
13
27
|
//# sourceMappingURL=config.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.mjs","names":["Errors.DomainKitError","Reason.InvalidInput"],"sources":["../../src/internal/config.ts"],"sourcesContent":["import { Config, Effect } from \"effect\";\n\nimport * as Errors from \"./error.ts\";\nimport * as Reason from \"../Reason.ts\";\n\n/** Accept a literal or a `Config`; config failures surface as `InvalidInput` on `field`. */\nexport const resolve = <A>(\n value: A | Config.Config<A>,\n field: string,\n): Effect.Effect<A, Errors.DomainKitError> =>\n Config.isConfig(value)\n ? value.pipe(\n Effect.mapError(\n (
|
|
1
|
+
{"version":3,"file":"config.mjs","names":["Errors.DomainKitError","Reason.InvalidInput","Errors.fail"],"sources":["../../src/internal/config.ts"],"sourcesContent":["import { Config, Effect, Redacted } from \"effect\";\n\nimport * as Errors from \"./error.ts\";\nimport * as Reason from \"../Reason.ts\";\n\n/** Accept a literal or a `Config`; config failures surface as `InvalidInput` on `field`. */\nexport const resolve = <A>(\n value: A | Config.Config<A>,\n field: string,\n): Effect.Effect<A, Errors.DomainKitError> =>\n Config.isConfig(value)\n ? value.pipe(\n Effect.mapError(\n () =>\n new Errors.DomainKitError({\n reason: new Reason.InvalidInput({ message: `Could not resolve ${field}`, field }),\n }),\n ),\n )\n : Effect.succeed(value);\n\n/** Configuration diagnostics name the field, never the rejected value. */\nexport const requireValue = <A>(\n value: A,\n valid: (value: A) => boolean,\n field: string,\n message: string,\n): Effect.Effect<A, Errors.DomainKitError> =>\n Effect.suspend(() =>\n valid(value) ? Effect.succeed(value) : Errors.fail(new Reason.InvalidInput({ field, message })),\n );\n\nexport const nonempty = (value: string, field: string) =>\n requireValue(\n value,\n (v) => typeof v === \"string\" && v.trim().length > 0,\n field,\n `${field} must be a nonempty string`,\n );\n\nexport const secret = (value: Redacted.Redacted<string>, field: string) =>\n requireValue(\n value,\n (v) =>\n Redacted.isRedacted(v) &&\n typeof Redacted.value(v) === \"string\" &&\n Redacted.value(v).trim().length > 0,\n field,\n `${field} must be a nonempty Redacted string`,\n );\n\n/** Paths and split emulator origins are supported; URL suffixes must remain paths. */\nexport const endpoint = (value: string, field: string) =>\n requireValue(\n value,\n (v) => {\n if (\n typeof v !== \"string\" ||\n v !== v.trim() ||\n [...v].some((character) => character.charCodeAt(0) < 0x20)\n )\n return false;\n const url = URL.parse(v);\n return (\n url !== null &&\n (url.protocol === \"https:\" || url.protocol === \"http:\") &&\n url.username === \"\" &&\n url.password === \"\" &&\n !url.href.includes(\"?\") &&\n !url.href.includes(\"#\")\n );\n },\n field,\n `${field} must be an HTTP(S) URL without surrounding whitespace, control characters, credentials, query, or fragment`,\n );\n\nexport const scopes = (value: ReadonlyArray<string>, field: string) =>\n requireValue(\n value,\n (v) =>\n Array.isArray(v) &&\n v.every((scope) => typeof scope === \"string\" && /^[\\x21\\x23-\\x5B\\x5D-\\x7E]+$/.test(scope)),\n field,\n `${field} must contain valid OAuth scope tokens`,\n );\n"],"mappings":";;;;;AAMA,MAAa,WACX,OACA,UAEA,OAAO,SAAS,KAAK,IACjB,MAAM,KACJ,OAAO,eAEH,IAAIA,eAAsB,EACxB,QAAQ,IAAIC,aAAoB;CAAE,SAAS,qBAAqB;CAAS;AAAM,CAAC,EAClF,CAAC,CACL,CACF,IACA,OAAO,QAAQ,KAAK;;AAG1B,MAAa,gBACX,OACA,OACA,OACA,YAEA,OAAO,cACL,MAAM,KAAK,IAAI,OAAO,QAAQ,KAAK,IAAIC,KAAY,IAAID,aAAoB;CAAE;CAAO;AAAQ,CAAC,CAAC,CAChG;AAEF,MAAa,YAAY,OAAe,UACtC,aACE,QACC,MAAM,OAAO,MAAM,YAAY,EAAE,KAAK,CAAC,CAAC,SAAS,GAClD,OACA,GAAG,MAAM,2BACX;AAEF,MAAa,UAAU,OAAkC,UACvD,aACE,QACC,MACC,SAAS,WAAW,CAAC,KACrB,OAAO,SAAS,MAAM,CAAC,MAAM,YAC7B,SAAS,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS,GACpC,OACA,GAAG,MAAM,oCACX;;AAGF,MAAa,YAAY,OAAe,UACtC,aACE,QACC,MAAM;CACL,IACE,OAAO,MAAM,YACb,MAAM,EAAE,KAAK,KACb,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,cAAc,UAAU,WAAW,CAAC,IAAI,EAAI,GAEzD,OAAO;CACT,MAAM,MAAM,IAAI,MAAM,CAAC;CACvB,OACE,QAAQ,SACP,IAAI,aAAa,YAAY,IAAI,aAAa,YAC/C,IAAI,aAAa,MACjB,IAAI,aAAa,MACjB,CAAC,IAAI,KAAK,SAAS,GAAG,KACtB,CAAC,IAAI,KAAK,SAAS,GAAG;AAE1B,GACA,OACA,GAAG,MAAM,4GACX;AAEF,MAAa,UAAU,OAA8B,UACnD,aACE,QACC,MACC,MAAM,QAAQ,CAAC,KACf,EAAE,OAAO,UAAU,OAAO,UAAU,YAAY,8BAA8B,KAAK,KAAK,CAAC,GAC3F,OACA,GAAG,MAAM,uCACX"}
|
|
@@ -60,6 +60,7 @@ const fromAsync = (definition) => {
|
|
|
60
60
|
}) },
|
|
61
61
|
...oauth === void 0 ? {} : { oauth: {
|
|
62
62
|
label: oauth.label,
|
|
63
|
+
...oauth.registration === void 0 ? {} : { registration: oauth.registration },
|
|
63
64
|
scopes: oauth.scopes,
|
|
64
65
|
start: (input) => call(() => oauth.start(input)),
|
|
65
66
|
complete: (input) => call(() => oauth.complete(input)).pipe(Effect.map(issued)),
|
|
@@ -68,6 +69,7 @@ const fromAsync = (definition) => {
|
|
|
68
69
|
} },
|
|
69
70
|
...integration === void 0 ? {} : { integration: {
|
|
70
71
|
label: integration.label,
|
|
72
|
+
...integration.registration === void 0 ? {} : { registration: integration.registration },
|
|
71
73
|
start: (input) => call(() => integration.start(input)),
|
|
72
74
|
complete: (input) => call(() => integration.complete(input)).pipe(Effect.map(issued)),
|
|
73
75
|
...integrationRefresh === void 0 ? {} : { refresh: (credential) => call(() => integrationRefresh(plain(credential))).pipe(Effect.map(issued)) },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-async.mjs","names":["Errors.isDomainKitError","Errors.DomainKitError","Reason.ProviderUnavailable","Provider.make","Provider.tokenAuth"],"sources":["../../src/internal/provider-async.ts"],"sourcesContent":["import { DateTime, Effect, Redacted, type Schema } from \"effect\";\n\nimport * as Errors from \"./error.ts\";\nimport * as Reason from \"../Reason.ts\";\nimport type * as DnsRecord from \"../DnsRecord.ts\";\nimport * as Provider from \"../Provider.ts\";\nimport type { Capability } from \"../Storage.ts\";\n\n/** Plain-object credential for Promise-shaped providers; `secret` is the raw string. */\nexport interface AsyncCredential {\n readonly secret: string;\n readonly context: unknown;\n}\n\nexport interface AsyncIssuedCredential extends AsyncCredential {\n readonly expiresAt: Date | null;\n}\n\nexport interface AsyncDns {\n readonly list: (zone: string) => Promise<ReadonlyArray<Provider.ObservedRecord>>;\n readonly create: (\n zone: string,\n record: DnsRecord.Model,\n ) => Promise<{ readonly providerRecordId: string }>;\n readonly get: (zone: string, providerRecordId: string) => Promise<DnsRecord.Observed | null>;\n readonly delete: (zone: string, providerRecordId: string) => Promise<void>;\n}\n\nexport interface AsyncSession {\n readonly listTargets: () => Promise<ReadonlyArray<Provider.Target>>;\n readonly resolveTarget: (domain: string) => Promise<Provider.Resolution>;\n readonly dns: (target: Provider.Target) => AsyncDns;\n readonly capabilities: () => Promise<ReadonlyArray<Capability>>;\n}\n\nexport interface AsyncTokenAuth {\n readonly label: string;\n readonly docsUrl?: string;\n readonly requiredCapabilities: ReadonlyArray<Capability>;\n readonly fields: Schema.Struct<Provider.TokenFieldSchemas>;\n /** Decoded field values with secrets unwrapped to strings. */\n readonly authenticate: (\n values: Readonly<Record<string, string | undefined>>,\n ) => Promise<AsyncIssuedCredential>;\n}\n\nexport interface AsyncOAuthAuth {\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 }) => Promise<{ readonly authorizationUrl: string }>;\n readonly complete: (\n input: Provider.CallbackInput & { readonly codeVerifier: string },\n ) => Promise<AsyncIssuedCredential>;\n readonly refresh: (credential: AsyncCredential) => Promise<AsyncIssuedCredential>;\n readonly revoke?: (credential: AsyncCredential) => Promise<void>;\n}\n\nexport interface AsyncIntegrationAuth {\n readonly label: string;\n readonly start: (input: {\n readonly state: string;\n readonly callbackUrl: string;\n }) => Promise<{ readonly authorizationUrl: string }>;\n readonly complete: (input: Provider.CallbackInput) => Promise<AsyncIssuedCredential>;\n readonly refresh?: (credential: AsyncCredential) => Promise<AsyncIssuedCredential>;\n readonly revoke?: (credential: AsyncCredential) => Promise<void>;\n}\n\n/** `Provider.Definition` with every operation returning a Promise. */\nexport interface AsyncDefinition<Context = unknown> {\n readonly id: string;\n readonly name: string;\n readonly auth: {\n readonly token?: AsyncTokenAuth;\n readonly oauth?: AsyncOAuthAuth;\n readonly integration?: AsyncIntegrationAuth;\n };\n readonly context: Schema.Codec<Context, unknown, never, never>;\n readonly contextVersion: string;\n readonly migrateContext?: (envelope: Provider.Envelope) => Promise<Context>;\n readonly session: (credential: AsyncCredential) => AsyncSession;\n}\n\nconst failure = (provider: string) => (cause: unknown) =>\n Errors.isDomainKitError(cause)\n ? cause\n : new Errors.DomainKitError({\n reason: new Reason.ProviderUnavailable({\n provider,\n message: cause instanceof Error ? cause.message : `${provider} request failed`,\n }),\n });\n\nconst issued = (value: AsyncIssuedCredential): Provider.IssuedCredential => ({\n secret: Redacted.make(value.secret),\n context: value.context,\n expiresAt: value.expiresAt === null ? null : DateTime.fromDateUnsafe(value.expiresAt),\n});\n\nconst plain = (credential: Provider.Credential): AsyncCredential => ({\n secret: Redacted.value(credential.secret),\n context: credential.context,\n});\n\n/** Adapt a Promise-shaped provider; rejections become `DomainKit.Error` (a thrown one passes through). */\nexport const fromAsync = <Context>(\n definition: AsyncDefinition<Context>,\n): Provider.Definition<Context> => {\n const call = <A>(run: () => Promise<A>): Provider.Fx<A> =>\n Effect.tryPromise({ try: run, catch: failure(definition.id) });\n const dns = (target: Provider.Target, inner: AsyncDns): Provider.Dns => ({\n list: (zone) => call(() => inner.list(zone)),\n create: (zone, record) => call(() => inner.create(zone, record)),\n get: (zone, providerRecordId) => call(() => inner.get(zone, providerRecordId)),\n delete: (zone, providerRecordId) => call(() => inner.delete(zone, providerRecordId)),\n });\n const session = (credential: Provider.Credential): Provider.Session => {\n const inner = definition.session(plain(credential));\n return {\n listTargets: () => call(() => inner.listTargets()),\n resolveTarget: (domain) => call(() => inner.resolveTarget(domain)),\n capabilities: () => call(() => inner.capabilities()),\n dns: (target) => dns(target, inner.dns(target)),\n };\n };\n const token = definition.auth.token;\n const oauth = definition.auth.oauth;\n const integration = definition.auth.integration;\n const migrate = definition.migrateContext;\n const oauthRevoke = oauth?.revoke;\n const integrationRefresh = integration?.refresh;\n const integrationRevoke = integration?.revoke;\n return Provider.make<Context>({\n id: definition.id,\n name: definition.name,\n context: definition.context,\n contextVersion: definition.contextVersion,\n ...(migrate === undefined\n ? {}\n : {\n migrateContext: (envelope: Provider.Envelope) => call(() => migrate(envelope)),\n }),\n auth: {\n ...(token === undefined\n ? {}\n : {\n token: Provider.tokenAuth({\n label: token.label,\n ...(token.docsUrl === undefined ? {} : { docsUrl: token.docsUrl }),\n requiredCapabilities: token.requiredCapabilities,\n fields: token.fields,\n authenticate: (values) =>\n call(() =>\n token.authenticate(\n Object.fromEntries(\n Object.entries(values as Record<string, unknown>).map(([name, value]) => [\n name,\n Redacted.isRedacted(value)\n ? String(Redacted.value(value))\n : value === undefined\n ? undefined\n : String(value),\n ]),\n ),\n ),\n ).pipe(Effect.map(issued)),\n }),\n }),\n ...(oauth === undefined\n ? {}\n : {\n oauth: {\n label: oauth.label,\n scopes: oauth.scopes,\n start: (input) => call(() => oauth.start(input)),\n complete: (input) => call(() => oauth.complete(input)).pipe(Effect.map(issued)),\n refresh: (credential) =>\n call(() => oauth.refresh(plain(credential))).pipe(Effect.map(issued)),\n ...(oauthRevoke === undefined\n ? {}\n : {\n revoke: (credential: Provider.Credential) =>\n call(() => oauthRevoke(plain(credential))),\n }),\n } satisfies Provider.OAuthAuth,\n }),\n ...(integration === undefined\n ? {}\n : {\n integration: {\n label: integration.label,\n start: (input) => call(() => integration.start(input)),\n complete: (input) => call(() => integration.complete(input)).pipe(Effect.map(issued)),\n ...(integrationRefresh === undefined\n ? {}\n : {\n refresh: (credential: Provider.Credential) =>\n call(() => integrationRefresh(plain(credential))).pipe(Effect.map(issued)),\n }),\n ...(integrationRevoke === undefined\n ? {}\n : {\n revoke: (credential: Provider.Credential) =>\n call(() => integrationRevoke(plain(credential))),\n }),\n } satisfies Provider.IntegrationAuth,\n }),\n },\n session,\n });\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"provider-async.mjs","names":["Errors.isDomainKitError","Errors.DomainKitError","Reason.ProviderUnavailable","Provider.make","Provider.tokenAuth"],"sources":["../../src/internal/provider-async.ts"],"sourcesContent":["import { DateTime, Effect, Redacted, type Schema } from \"effect\";\n\nimport * as Errors from \"./error.ts\";\nimport * as Reason from \"../Reason.ts\";\nimport type * as DnsRecord from \"../DnsRecord.ts\";\nimport * as Provider from \"../Provider.ts\";\nimport type { Capability } from \"../Storage.ts\";\n\n/** Plain-object credential for Promise-shaped providers; `secret` is the raw string. */\nexport interface AsyncCredential {\n readonly secret: string;\n readonly context: unknown;\n}\n\nexport interface AsyncIssuedCredential extends AsyncCredential {\n readonly expiresAt: Date | null;\n}\n\nexport interface AsyncDns {\n readonly list: (zone: string) => Promise<ReadonlyArray<Provider.ObservedRecord>>;\n readonly create: (\n zone: string,\n record: DnsRecord.Model,\n ) => Promise<{ readonly providerRecordId: string }>;\n readonly get: (zone: string, providerRecordId: string) => Promise<DnsRecord.Observed | null>;\n readonly delete: (zone: string, providerRecordId: string) => Promise<void>;\n}\n\nexport interface AsyncSession {\n readonly listTargets: () => Promise<ReadonlyArray<Provider.Target>>;\n readonly resolveTarget: (domain: string) => Promise<Provider.Resolution>;\n readonly dns: (target: Provider.Target) => AsyncDns;\n readonly capabilities: () => Promise<ReadonlyArray<Capability>>;\n}\n\nexport interface AsyncTokenAuth {\n readonly label: string;\n readonly docsUrl?: string;\n readonly requiredCapabilities: ReadonlyArray<Capability>;\n readonly fields: Schema.Struct<Provider.TokenFieldSchemas>;\n /** Decoded field values with secrets unwrapped to strings. */\n readonly authenticate: (\n values: Readonly<Record<string, string | undefined>>,\n ) => Promise<AsyncIssuedCredential>;\n}\n\nexport interface AsyncOAuthAuth {\n readonly registration?: Provider.OAuthAuth[\"registration\"];\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 }) => Promise<{ readonly authorizationUrl: string }>;\n readonly complete: (\n input: Provider.CallbackInput & { readonly codeVerifier: string },\n ) => Promise<AsyncIssuedCredential>;\n readonly refresh: (credential: AsyncCredential) => Promise<AsyncIssuedCredential>;\n readonly revoke?: (credential: AsyncCredential) => Promise<void>;\n}\n\nexport interface AsyncIntegrationAuth {\n readonly registration?: Provider.IntegrationAuth[\"registration\"];\n readonly label: string;\n readonly start: (input: {\n readonly state: string;\n readonly callbackUrl: string;\n }) => Promise<{ readonly authorizationUrl: string }>;\n readonly complete: (input: Provider.CallbackInput) => Promise<AsyncIssuedCredential>;\n readonly refresh?: (credential: AsyncCredential) => Promise<AsyncIssuedCredential>;\n readonly revoke?: (credential: AsyncCredential) => Promise<void>;\n}\n\n/** `Provider.Definition` with every operation returning a Promise. */\nexport interface AsyncDefinition<Context = unknown> {\n readonly id: string;\n readonly name: string;\n readonly auth: {\n readonly token?: AsyncTokenAuth;\n readonly oauth?: AsyncOAuthAuth;\n readonly integration?: AsyncIntegrationAuth;\n };\n readonly context: Schema.Codec<Context, unknown, never, never>;\n readonly contextVersion: string;\n readonly migrateContext?: (envelope: Provider.Envelope) => Promise<Context>;\n readonly session: (credential: AsyncCredential) => AsyncSession;\n}\n\nconst failure = (provider: string) => (cause: unknown) =>\n Errors.isDomainKitError(cause)\n ? cause\n : new Errors.DomainKitError({\n reason: new Reason.ProviderUnavailable({\n provider,\n message: cause instanceof Error ? cause.message : `${provider} request failed`,\n }),\n });\n\nconst issued = (value: AsyncIssuedCredential): Provider.IssuedCredential => ({\n secret: Redacted.make(value.secret),\n context: value.context,\n expiresAt: value.expiresAt === null ? null : DateTime.fromDateUnsafe(value.expiresAt),\n});\n\nconst plain = (credential: Provider.Credential): AsyncCredential => ({\n secret: Redacted.value(credential.secret),\n context: credential.context,\n});\n\n/** Adapt a Promise-shaped provider; rejections become `DomainKit.Error` (a thrown one passes through). */\nexport const fromAsync = <Context>(\n definition: AsyncDefinition<Context>,\n): Provider.Definition<Context> => {\n const call = <A>(run: () => Promise<A>): Provider.Fx<A> =>\n Effect.tryPromise({ try: run, catch: failure(definition.id) });\n const dns = (target: Provider.Target, inner: AsyncDns): Provider.Dns => ({\n list: (zone) => call(() => inner.list(zone)),\n create: (zone, record) => call(() => inner.create(zone, record)),\n get: (zone, providerRecordId) => call(() => inner.get(zone, providerRecordId)),\n delete: (zone, providerRecordId) => call(() => inner.delete(zone, providerRecordId)),\n });\n const session = (credential: Provider.Credential): Provider.Session => {\n const inner = definition.session(plain(credential));\n return {\n listTargets: () => call(() => inner.listTargets()),\n resolveTarget: (domain) => call(() => inner.resolveTarget(domain)),\n capabilities: () => call(() => inner.capabilities()),\n dns: (target) => dns(target, inner.dns(target)),\n };\n };\n const token = definition.auth.token;\n const oauth = definition.auth.oauth;\n const integration = definition.auth.integration;\n const migrate = definition.migrateContext;\n const oauthRevoke = oauth?.revoke;\n const integrationRefresh = integration?.refresh;\n const integrationRevoke = integration?.revoke;\n return Provider.make<Context>({\n id: definition.id,\n name: definition.name,\n context: definition.context,\n contextVersion: definition.contextVersion,\n ...(migrate === undefined\n ? {}\n : {\n migrateContext: (envelope: Provider.Envelope) => call(() => migrate(envelope)),\n }),\n auth: {\n ...(token === undefined\n ? {}\n : {\n token: Provider.tokenAuth({\n label: token.label,\n ...(token.docsUrl === undefined ? {} : { docsUrl: token.docsUrl }),\n requiredCapabilities: token.requiredCapabilities,\n fields: token.fields,\n authenticate: (values) =>\n call(() =>\n token.authenticate(\n Object.fromEntries(\n Object.entries(values as Record<string, unknown>).map(([name, value]) => [\n name,\n Redacted.isRedacted(value)\n ? String(Redacted.value(value))\n : value === undefined\n ? undefined\n : String(value),\n ]),\n ),\n ),\n ).pipe(Effect.map(issued)),\n }),\n }),\n ...(oauth === undefined\n ? {}\n : {\n oauth: {\n label: oauth.label,\n ...(oauth.registration === undefined ? {} : { registration: oauth.registration }),\n scopes: oauth.scopes,\n start: (input) => call(() => oauth.start(input)),\n complete: (input) => call(() => oauth.complete(input)).pipe(Effect.map(issued)),\n refresh: (credential) =>\n call(() => oauth.refresh(plain(credential))).pipe(Effect.map(issued)),\n ...(oauthRevoke === undefined\n ? {}\n : {\n revoke: (credential: Provider.Credential) =>\n call(() => oauthRevoke(plain(credential))),\n }),\n } satisfies Provider.OAuthAuth,\n }),\n ...(integration === undefined\n ? {}\n : {\n integration: {\n label: integration.label,\n ...(integration.registration === undefined\n ? {}\n : { registration: integration.registration }),\n start: (input) => call(() => integration.start(input)),\n complete: (input) => call(() => integration.complete(input)).pipe(Effect.map(issued)),\n ...(integrationRefresh === undefined\n ? {}\n : {\n refresh: (credential: Provider.Credential) =>\n call(() => integrationRefresh(plain(credential))).pipe(Effect.map(issued)),\n }),\n ...(integrationRevoke === undefined\n ? {}\n : {\n revoke: (credential: Provider.Credential) =>\n call(() => integrationRevoke(plain(credential))),\n }),\n } satisfies Provider.IntegrationAuth,\n }),\n },\n session,\n });\n};\n"],"mappings":"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"}
|
package/dist/package.mjs
CHANGED