domainkit 0.20.1 → 0.22.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/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 = (options.baseUrl ?? "https://api.vercel.com").replace(/\/$/, "");
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 = (settings.installOrigin ?? "https://vercel.com").replace(/\/$/, "");
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
- start: (input) => {
45
- const authorizationUrl = new URL(`${installOrigin}/integrations/${encodeURIComponent(settings.slug)}/new`);
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 Effect.succeed({ authorizationUrl: authorizationUrl.toString() });
49
- },
59
+ return { authorizationUrl: authorizationUrl.toString() };
60
+ }),
50
61
  complete: (input) => Effect.gen(function* () {
51
- const clientId = yield* resolve(settings.clientId, "integration.clientId");
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,
@@ -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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"}
@@ -1,2 +1,3 @@
1
+ import { CallbackRecovery_exports } from "../CallbackRecovery.mjs";
1
2
  import { Transport_exports } from "../Transport.mjs";
2
- export { Transport_exports as Transport };
3
+ export { CallbackRecovery_exports as CallbackRecovery, Transport_exports as Transport };
@@ -1,2 +1 @@
1
- import { Server_exports } from "../Server.mjs";
2
- export { Server_exports as Server };
1
+ export { Server_exports as Server } from "../Server.mjs";
package/dist/index.mjs CHANGED
@@ -3,6 +3,7 @@ import { DnsRecord_exports } from "./DnsRecord.mjs";
3
3
  import { Plan_exports } from "./Plan.mjs";
4
4
  import { Reason_exports } from "./Reason.mjs";
5
5
  import { Approval_exports } from "./Approval.mjs";
6
+ import { CallbackRecovery_exports } from "./CallbackRecovery.mjs";
6
7
  import { Custody_exports } from "./Custody.mjs";
7
8
  import { Principal_exports } from "./Principal.mjs";
8
9
  import { Provider_exports } from "./Provider.mjs";
@@ -26,7 +27,7 @@ import { version } from "./package.mjs";
26
27
  * Lifecycle services: Connect, Provision, Cleanup, Verify.
27
28
  * Host seams: Storage, Custody, Principal.
28
29
  * Providers: Provider, Providers, Cloudflare, Vercel.
29
- * Values: DomainName, DnsRecord, Plan, Approval, Receipt.
30
+ * Values: DomainName, DnsRecord, Plan, Approval, Receipt, CallbackRecovery.
30
31
  * Everything else: Reason, Resolver, DomainKit (the composed layer and `DomainKit.Error`).
31
32
  *
32
33
  * Subpath: domainkit/testing.
@@ -34,6 +35,6 @@ import { version } from "./package.mjs";
34
35
  /** The current package version from the package manifest. */
35
36
  const VERSION = version;
36
37
  //#endregion
37
- export { Approval_exports as Approval, Cleanup_exports as Cleanup, Cloudflare_exports as Cloudflare, Connect_exports as Connect, Custody_exports as Custody, DnsRecord_exports as DnsRecord, DomainKit_exports as DomainKit, DomainName_exports as DomainName, Plan_exports as Plan, Principal_exports as Principal, Provider_exports as Provider, Providers_exports as Providers, Provision_exports as Provision, Reason_exports as Reason, Receipt_exports as Receipt, Resolver_exports as Resolver, Storage_exports as Storage, VERSION, Vercel_exports as Vercel, Verify_exports as Verify };
38
+ export { Approval_exports as Approval, CallbackRecovery_exports as CallbackRecovery, Cleanup_exports as Cleanup, Cloudflare_exports as Cloudflare, Connect_exports as Connect, Custody_exports as Custody, DnsRecord_exports as DnsRecord, DomainKit_exports as DomainKit, DomainName_exports as DomainName, Plan_exports as Plan, Principal_exports as Principal, Provider_exports as Provider, Providers_exports as Providers, Provision_exports as Provision, Reason_exports as Reason, Receipt_exports as Receipt, Resolver_exports as Resolver, Storage_exports as Storage, VERSION, Vercel_exports as Vercel, Verify_exports as Verify };
38
39
 
39
40
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["packageJson.version"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * domainkit — the Effect-native root. One module per concept: services expose `Service` (the tag)\n * and `Interface` (its shape), value modules expose `Model` (the schema).\n *\n * Lifecycle services: Connect, Provision, Cleanup, Verify.\n * Host seams: Storage, Custody, Principal.\n * Providers: Provider, Providers, Cloudflare, Vercel.\n * Values: DomainName, DnsRecord, Plan, Approval, Receipt.\n * Everything else: Reason, Resolver, DomainKit (the composed layer and `DomainKit.Error`).\n *\n * Subpath: domainkit/testing.\n */\nimport packageJson from \"../package.json\" with { type: \"json\" };\n\n/** The current package version from the package manifest. */\nexport const VERSION = packageJson.version;\n\nexport * as Approval from \"./Approval.ts\";\nexport * as Cleanup from \"./Cleanup.ts\";\nexport * as Cloudflare from \"./Cloudflare.ts\";\nexport * as Connect from \"./Connect.ts\";\nexport * as Custody from \"./Custody.ts\";\nexport * as DnsRecord from \"./DnsRecord.ts\";\nexport * as DomainKit from \"./DomainKit.ts\";\nexport * as DomainName from \"./DomainName.ts\";\nexport * as Plan from \"./Plan.ts\";\nexport * as Principal from \"./Principal.ts\";\nexport * as Provider from \"./Provider.ts\";\nexport * as Providers from \"./Providers.ts\";\nexport * as Provision from \"./Provision.ts\";\nexport * as Reason from \"./Reason.ts\";\nexport * as Receipt from \"./Receipt.ts\";\nexport * as Resolver from \"./Resolver.ts\";\nexport * as Storage from \"./Storage.ts\";\nexport * as Vercel from \"./Vercel.ts\";\nexport * as Verify from \"./Verify.ts\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAeA,MAAa,UAAUA"}
1
+ {"version":3,"file":"index.mjs","names":["packageJson.version"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * domainkit — the Effect-native root. One module per concept: services expose `Service` (the tag)\n * and `Interface` (its shape), value modules expose `Model` (the schema).\n *\n * Lifecycle services: Connect, Provision, Cleanup, Verify.\n * Host seams: Storage, Custody, Principal.\n * Providers: Provider, Providers, Cloudflare, Vercel.\n * Values: DomainName, DnsRecord, Plan, Approval, Receipt, CallbackRecovery.\n * Everything else: Reason, Resolver, DomainKit (the composed layer and `DomainKit.Error`).\n *\n * Subpath: domainkit/testing.\n */\nimport packageJson from \"../package.json\" with { type: \"json\" };\n\n/** The current package version from the package manifest. */\nexport const VERSION = packageJson.version;\n\nexport * as Approval from \"./Approval.ts\";\nexport * as CallbackRecovery from \"./CallbackRecovery.ts\";\nexport * as Cleanup from \"./Cleanup.ts\";\nexport * as Cloudflare from \"./Cloudflare.ts\";\nexport * as Connect from \"./Connect.ts\";\nexport * as Custody from \"./Custody.ts\";\nexport * as DnsRecord from \"./DnsRecord.ts\";\nexport * as DomainKit from \"./DomainKit.ts\";\nexport * as DomainName from \"./DomainName.ts\";\nexport * as Plan from \"./Plan.ts\";\nexport * as Principal from \"./Principal.ts\";\nexport * as Provider from \"./Provider.ts\";\nexport * as Providers from \"./Providers.ts\";\nexport * as Provision from \"./Provision.ts\";\nexport * as Reason from \"./Reason.ts\";\nexport * as Receipt from \"./Receipt.ts\";\nexport * as Resolver from \"./Resolver.ts\";\nexport * as Storage from \"./Storage.ts\";\nexport * as Vercel from \"./Vercel.ts\";\nexport * as Verify from \"./Verify.ts\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAeA,MAAa,UAAUA"}
@@ -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"}
@@ -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((cause) => new DomainKitError({ reason: new InvalidInput({
7
- message: cause.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 (cause) =>\n new Errors.DomainKitError({\n reason: new Reason.InvalidInput({ message: cause.message, field }),\n }),\n ),\n )\n : Effect.succeed(value);\n"],"mappings":";;;;;AAMA,MAAa,WACX,OACA,UAEA,OAAO,SAAS,KAAK,IACjB,MAAM,KACJ,OAAO,UACJ,UACC,IAAIA,eAAsB,EACxB,QAAQ,IAAIC,aAAoB;CAAE,SAAS,MAAM;CAAS;AAAM,CAAC,EACnE,CAAC,CACL,CACF,IACA,OAAO,QAAQ,KAAK"}
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
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.20.1";
2
+ var version = "0.22.0";
3
3
  //#endregion
4
4
  export { version };
5
5
 
@@ -0,0 +1,12 @@
1
+ /** Browser-safe recovery hints for returning from an authenticated provider callback. */
2
+ import { Schema } from "effect";
3
+ export declare const Model: Schema.Struct<{
4
+ readonly kind: Schema.Literals<readonly ["cancelled", "restart", "inspect"]>;
5
+ readonly provider: Schema.String;
6
+ readonly connectionId: Schema.optionalKey<Schema.String>;
7
+ }>;
8
+ export type Model = typeof Model.Type;
9
+ /** Reads a complete recovery hint; ignores missing, duplicate, or invalid metadata. */
10
+ export declare const read: (url: URL) => Model | null;
11
+ /** Clones the URL and replaces or clears all recovery hints, preserving other parameters and hash. */
12
+ export declare const write: (url: URL, outcome: Model | null) => URL;
@@ -233,6 +233,127 @@ export declare const Started: {
233
233
  readonly candidates: ReadonlyArray<Provider.Target>;
234
234
  }, "_tag">;
235
235
  };
236
+ /** A verified completion result. Errors stay on the server; do not serialize them to a browser. */
237
+ export type Completion = Data.TaggedEnum<{
238
+ Connected: {
239
+ readonly started: Exclude<Started, {
240
+ readonly _tag: "Redirect";
241
+ }>;
242
+ };
243
+ Cancelled: {
244
+ readonly error: Errors.DomainKitError;
245
+ readonly connectionId?: string;
246
+ };
247
+ Failed: {
248
+ readonly error: Errors.DomainKitError;
249
+ readonly recovery: "restart" | "inspect";
250
+ readonly connectionId?: string;
251
+ };
252
+ }>;
253
+ export declare const Completion: {
254
+ readonly $is: <Tag extends "Cancelled" | "Connected" | "Failed">(tag: Tag) => (u: unknown) => u is Extract<{
255
+ readonly _tag: "Cancelled";
256
+ readonly error: Errors.DomainKitError;
257
+ readonly connectionId?: string;
258
+ }, {
259
+ readonly _tag: Tag;
260
+ }> | Extract<{
261
+ readonly _tag: "Connected";
262
+ readonly started: Exclude<Started, {
263
+ readonly _tag: "Redirect";
264
+ }>;
265
+ }, {
266
+ readonly _tag: Tag;
267
+ }> | Extract<{
268
+ readonly _tag: "Failed";
269
+ readonly error: Errors.DomainKitError;
270
+ readonly recovery: "restart" | "inspect";
271
+ readonly connectionId?: string;
272
+ }, {
273
+ readonly _tag: Tag;
274
+ }>;
275
+ readonly $match: {
276
+ <Cases extends { readonly [Tag in "Cancelled" | "Connected" | "Failed"]: (args: Extract<{
277
+ readonly _tag: "Cancelled";
278
+ readonly error: Errors.DomainKitError;
279
+ readonly connectionId?: string;
280
+ } | {
281
+ readonly _tag: "Connected";
282
+ readonly started: Exclude<Started, {
283
+ readonly _tag: "Redirect";
284
+ }>;
285
+ } | {
286
+ readonly _tag: "Failed";
287
+ readonly error: Errors.DomainKitError;
288
+ readonly recovery: "restart" | "inspect";
289
+ readonly connectionId?: string;
290
+ }, {
291
+ readonly _tag: Tag;
292
+ }>) => any; }>(cases: Cases): (value: {
293
+ readonly _tag: "Cancelled";
294
+ readonly error: Errors.DomainKitError;
295
+ readonly connectionId?: string;
296
+ } | {
297
+ readonly _tag: "Connected";
298
+ readonly started: Exclude<Started, {
299
+ readonly _tag: "Redirect";
300
+ }>;
301
+ } | {
302
+ readonly _tag: "Failed";
303
+ readonly error: Errors.DomainKitError;
304
+ readonly recovery: "restart" | "inspect";
305
+ readonly connectionId?: string;
306
+ }) => import("effect/Unify").Unify<ReturnType<Cases["Cancelled" | "Connected" | "Failed"]>>;
307
+ <Cases extends { readonly [Tag in "Cancelled" | "Connected" | "Failed"]: (args: Extract<{
308
+ readonly _tag: "Cancelled";
309
+ readonly error: Errors.DomainKitError;
310
+ readonly connectionId?: string;
311
+ } | {
312
+ readonly _tag: "Connected";
313
+ readonly started: Exclude<Started, {
314
+ readonly _tag: "Redirect";
315
+ }>;
316
+ } | {
317
+ readonly _tag: "Failed";
318
+ readonly error: Errors.DomainKitError;
319
+ readonly recovery: "restart" | "inspect";
320
+ readonly connectionId?: string;
321
+ }, {
322
+ readonly _tag: Tag;
323
+ }>) => any; }>(value: {
324
+ readonly _tag: "Cancelled";
325
+ readonly error: Errors.DomainKitError;
326
+ readonly connectionId?: string;
327
+ } | {
328
+ readonly _tag: "Connected";
329
+ readonly started: Exclude<Started, {
330
+ readonly _tag: "Redirect";
331
+ }>;
332
+ } | {
333
+ readonly _tag: "Failed";
334
+ readonly error: Errors.DomainKitError;
335
+ readonly recovery: "restart" | "inspect";
336
+ readonly connectionId?: string;
337
+ }, cases: Cases): import("effect/Unify").Unify<ReturnType<Cases["Cancelled" | "Connected" | "Failed"]>>;
338
+ };
339
+ readonly Cancelled: Data.TaggedEnum.ConstructorFrom<{
340
+ readonly _tag: "Cancelled";
341
+ readonly error: Errors.DomainKitError;
342
+ readonly connectionId?: string;
343
+ }, "_tag">;
344
+ readonly Connected: Data.TaggedEnum.ConstructorFrom<{
345
+ readonly _tag: "Connected";
346
+ readonly started: Exclude<Started, {
347
+ readonly _tag: "Redirect";
348
+ }>;
349
+ }, "_tag">;
350
+ readonly Failed: Data.TaggedEnum.ConstructorFrom<{
351
+ readonly _tag: "Failed";
352
+ readonly error: Errors.DomainKitError;
353
+ readonly recovery: "restart" | "inspect";
354
+ readonly connectionId?: string;
355
+ }, "_tag">;
356
+ };
236
357
  /** `attach` either attached the domain or needs the caller to pick a zone. */
237
358
  export type Selection = Data.TaggedEnum<{
238
359
  SelectionRequired: {
@@ -477,6 +598,16 @@ export interface Interface {
477
598
  readonly continuationId: string;
478
599
  readonly callbackUrl: string;
479
600
  }) => Fx<Started>;
601
+ /**
602
+ * Classify only after live owner/actor verification inside the completion lock. Optional
603
+ * `provider` binds the flow to a mounted route. Invalid flows fail without an outcome.
604
+ * Cancellation and failure leave the continuation retryable; persistence is not atomic.
605
+ */
606
+ readonly completeOutcome: (input: {
607
+ readonly continuationId: string;
608
+ readonly callbackUrl: string;
609
+ readonly provider?: string;
610
+ }) => Fx<Completion>;
480
611
  readonly attach: (input: {
481
612
  readonly connectionId: string;
482
613
  readonly domain: string;
@@ -586,6 +717,25 @@ export declare const complete: (input: {
586
717
  readonly connection: Storage.Connection;
587
718
  readonly candidates: ReadonlyArray<Provider.Target>;
588
719
  }, Errors.DomainKitError, Principal.Service | Service>;
720
+ export declare const completeOutcome: (input: {
721
+ readonly continuationId: string;
722
+ readonly callbackUrl: string;
723
+ readonly provider?: string;
724
+ }) => Effect.Effect<{
725
+ readonly _tag: "Cancelled";
726
+ readonly error: Errors.DomainKitError;
727
+ readonly connectionId?: string;
728
+ } | {
729
+ readonly _tag: "Connected";
730
+ readonly started: Exclude<Started, {
731
+ readonly _tag: "Redirect";
732
+ }>;
733
+ } | {
734
+ readonly _tag: "Failed";
735
+ readonly error: Errors.DomainKitError;
736
+ readonly recovery: "restart" | "inspect";
737
+ readonly connectionId?: string;
738
+ }, Errors.DomainKitError, Principal.Service | Service>;
589
739
  export declare const attach: (input: {
590
740
  readonly connectionId: string;
591
741
  readonly domain: string;
@@ -189,6 +189,11 @@ export interface CallbackInput {
189
189
  readonly params: Readonly<Record<string, string>>;
190
190
  }
191
191
  export interface OAuthAuth {
192
+ /** Optional setup requirements; absence makes offline registration settings unsupported. */
193
+ readonly registration?: {
194
+ readonly clientAuth: "none" | "client_secret_basic" | "client_secret_post";
195
+ readonly pkce: "S256";
196
+ };
192
197
  readonly label: string;
193
198
  readonly scopes: ReadonlyArray<string>;
194
199
  readonly start: (input: {
@@ -206,6 +211,10 @@ export interface OAuthAuth {
206
211
  }
207
212
  /** Marketplace-style installs (Vercel integrations) that redirect but are not OAuth. */
208
213
  export interface IntegrationAuth {
214
+ /** Authentication used by the integration code exchange, without credentials. */
215
+ readonly registration?: {
216
+ readonly clientAuth: "client_secret_post";
217
+ };
209
218
  readonly label: string;
210
219
  readonly start: (input: {
211
220
  readonly state: string;
@@ -24,6 +24,7 @@ import * as DnsRecord from "./DnsRecord.ts";
24
24
  import * as Errors from "./internal/error.ts";
25
25
  import * as Plan from "./Plan.ts";
26
26
  import * as Principal from "./Principal.ts";
27
+ import type * as Provider from "./Provider.ts";
27
28
  import * as Providers from "./Providers.ts";
28
29
  import * as Provision from "./Provision.ts";
29
30
  import * as Reason from "./Reason.ts";
@@ -11393,6 +11394,25 @@ export declare const group: HttpApiGroup.HttpApiGroup<"domainkit", HttpApiEndpoi
11393
11394
  export type Group = typeof group;
11394
11395
  /** Every route in the group, by name; what `Identity.authorize` is asked about. */
11395
11396
  export type EndpointName = HttpApiGroup.Endpoints<Group>["identifier"];
11397
+ /** Browser-safe summaries of an authenticated live callback. Connected is not DNS readiness. */
11398
+ export declare const CallbackOutcome: Schema.Union<readonly [Schema.Struct<{
11399
+ readonly _tag: Schema.Literal<"Connected">;
11400
+ readonly provider: Schema.String;
11401
+ readonly connectionId: Schema.String;
11402
+ readonly returnTo: Schema.String;
11403
+ }>, Schema.Struct<{
11404
+ readonly _tag: Schema.Literal<"Cancelled">;
11405
+ readonly provider: Schema.String;
11406
+ readonly connectionId: Schema.optionalKey<Schema.String>;
11407
+ readonly returnTo: Schema.String;
11408
+ }>, Schema.Struct<{
11409
+ readonly _tag: Schema.Literal<"Failed">;
11410
+ readonly provider: Schema.String;
11411
+ readonly connectionId: Schema.optionalKey<Schema.String>;
11412
+ readonly returnTo: Schema.String;
11413
+ readonly recovery: Schema.Literals<readonly ["restart", "inspect"]>;
11414
+ }>]>;
11415
+ export type CallbackOutcome = typeof CallbackOutcome.Type;
11396
11416
  export interface Options {
11397
11417
  /**
11398
11418
  * Where the callback route is reachable from the provider, without the `/callback/:provider`
@@ -11401,7 +11421,35 @@ export interface Options {
11401
11421
  readonly callbackBaseUrl?: string;
11402
11422
  /** Where interactive flows return when the callback carries no `returnTo`. */
11403
11423
  readonly defaultReturnTo?: string;
11424
+ /**
11425
+ * Select a same-origin destination, or return undefined to use the saved/default returnTo.
11426
+ * Default recovery redirects include CallbackRecovery hints; Connected clears those hints. Invoked
11427
+ * only for a verified live flow; no request, state, code, secrets, or storage rows reach this
11428
+ * policy. Close over acquired host services; its Effect requires no environment.
11429
+ * A policy failure after Connected cannot undo persistence: inspect before restarting.
11430
+ */
11431
+ readonly callback?: (outcome: CallbackOutcome) => Effect.Effect<string | undefined, Errors.DomainKitError>;
11404
11432
  }
11433
+ /** Offline registration requirements, not proof of provider console or grant readiness. */
11434
+ export declare const RegistrationSettings: Schema.Union<readonly [Schema.Struct<{
11435
+ readonly _tag: Schema.Literal<"OAuth">;
11436
+ readonly provider: Schema.String;
11437
+ readonly callbackUrl: Schema.String;
11438
+ readonly clientAuth: Schema.Literals<readonly ["none", "client_secret_basic", "client_secret_post"]>;
11439
+ readonly pkce: Schema.Literal<"S256">;
11440
+ readonly scopes: Schema.$Array<Schema.String>;
11441
+ }>, Schema.Struct<{
11442
+ readonly _tag: Schema.Literal<"Integration">;
11443
+ readonly provider: Schema.String;
11444
+ readonly callbackUrl: Schema.String;
11445
+ readonly clientAuth: Schema.Literal<"client_secret_post">;
11446
+ }>]>;
11447
+ export type RegistrationSettings = typeof RegistrationSettings.Type;
11448
+ /** Derive only public setup values; no credentials, identity, storage, or network are needed. */
11449
+ export declare const registrationSettings: (input: {
11450
+ readonly providers: ReadonlyArray<Provider.Definition>;
11451
+ readonly callbackBaseUrl: string;
11452
+ }) => Effect.Effect<ReadonlyArray<RegistrationSettings>, Errors.DomainKitError>;
11405
11453
  /** Everything the handlers need: the lifecycle services, Storage, and the host's `Identity`. */
11406
11454
  export type Services = Provision.Service | Cleanup.Service | Connect.Service | Verify.Service | Providers.Service | Storage.Service | Identity;
11407
11455
  /**
@@ -1,2 +1,3 @@
1
1
  /** domainkit/client */
2
+ export * as CallbackRecovery from "../CallbackRecovery.ts";
2
3
  export * as Transport from "../Transport.ts";
@@ -1,6 +1,7 @@
1
1
  /** The current package version from the package manifest. */
2
2
  export declare const VERSION: string;
3
3
  export * as Approval from "./Approval.ts";
4
+ export * as CallbackRecovery from "./CallbackRecovery.ts";
4
5
  export * as Cleanup from "./Cleanup.ts";
5
6
  export * as Cloudflare from "./Cloudflare.ts";
6
7
  export * as Connect from "./Connect.ts";
@@ -0,0 +1,4 @@
1
+ import * as Errors from "./error.ts";
2
+ /** A leaked flow id must reveal neither existence nor ownership, including read races. */
3
+ export declare const refuse: import("effect/Effect").Effect<never, Errors.DomainKitError, never>;
4
+ export declare const missing: (error: Errors.DomainKitError) => boolean;