domainkit 0.8.0 → 0.9.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.
Files changed (165) hide show
  1. package/README.md +213 -41
  2. package/dist/Approval.mjs +34 -0
  3. package/dist/Approval.mjs.map +1 -0
  4. package/dist/Cleanup.mjs +97 -0
  5. package/dist/Cleanup.mjs.map +1 -0
  6. package/dist/Cloudflare.mjs +225 -0
  7. package/dist/Cloudflare.mjs.map +1 -0
  8. package/dist/Connect.mjs +463 -0
  9. package/dist/Connect.mjs.map +1 -0
  10. package/dist/Custody.mjs +64 -0
  11. package/dist/Custody.mjs.map +1 -0
  12. package/dist/DnsRecord.mjs +213 -0
  13. package/dist/DnsRecord.mjs.map +1 -0
  14. package/dist/DomainKit.mjs +34 -0
  15. package/dist/DomainKit.mjs.map +1 -0
  16. package/dist/DomainName.mjs +100 -0
  17. package/dist/DomainName.mjs.map +1 -0
  18. package/dist/Plan.mjs +102 -0
  19. package/dist/Plan.mjs.map +1 -0
  20. package/dist/Principal.mjs +23 -0
  21. package/dist/Principal.mjs.map +1 -0
  22. package/dist/Provider.mjs +130 -0
  23. package/dist/Provider.mjs.map +1 -0
  24. package/dist/Providers.mjs +40 -0
  25. package/dist/Providers.mjs.map +1 -0
  26. package/dist/Provision.mjs +93 -0
  27. package/dist/Provision.mjs.map +1 -0
  28. package/dist/Reason.mjs +162 -0
  29. package/dist/Reason.mjs.map +1 -0
  30. package/dist/Receipt.mjs +71 -0
  31. package/dist/Receipt.mjs.map +1 -0
  32. package/dist/Resolver.mjs +75 -0
  33. package/dist/Resolver.mjs.map +1 -0
  34. package/dist/Server.mjs +516 -0
  35. package/dist/Server.mjs.map +1 -0
  36. package/dist/Storage.mjs +236 -0
  37. package/dist/Storage.mjs.map +1 -0
  38. package/dist/Testing.mjs +226 -0
  39. package/dist/Testing.mjs.map +1 -0
  40. package/dist/Transport.mjs +187 -0
  41. package/dist/Transport.mjs.map +1 -0
  42. package/dist/Vercel.mjs +116 -0
  43. package/dist/Vercel.mjs.map +1 -0
  44. package/dist/Verify.mjs +292 -0
  45. package/dist/Verify.mjs.map +1 -0
  46. package/dist/{rolldown-runtime-D7D4PA-g.mjs → _virtual/_rolldown/runtime.mjs} +1 -1
  47. package/dist/entry/client.mjs +2 -0
  48. package/dist/entry/server.mjs +2 -0
  49. package/dist/entry/testing.mjs +3 -0
  50. package/dist/index.mjs +33 -462
  51. package/dist/index.mjs.map +1 -1
  52. package/dist/internal/aes.mjs +61 -0
  53. package/dist/internal/aes.mjs.map +1 -0
  54. package/dist/internal/attempts.mjs +284 -0
  55. package/dist/internal/attempts.mjs.map +1 -0
  56. package/dist/internal/cloudflare/client.mjs +100 -0
  57. package/dist/internal/cloudflare/client.mjs.map +1 -0
  58. package/dist/internal/cloudflare/protocol.mjs +59 -0
  59. package/dist/internal/cloudflare/protocol.mjs.map +1 -0
  60. package/dist/internal/cloudflare/records.mjs +156 -0
  61. package/dist/internal/cloudflare/records.mjs.map +1 -0
  62. package/dist/internal/config.mjs +13 -0
  63. package/dist/internal/config.mjs.map +1 -0
  64. package/dist/internal/conformance/provider.mjs +207 -0
  65. package/dist/internal/conformance/provider.mjs.map +1 -0
  66. package/dist/internal/conformance/storage.mjs +428 -0
  67. package/dist/internal/conformance/storage.mjs.map +1 -0
  68. package/dist/internal/digest.mjs +25 -0
  69. package/dist/internal/digest.mjs.map +1 -0
  70. package/dist/internal/doh.mjs +152 -0
  71. package/dist/internal/doh.mjs.map +1 -0
  72. package/dist/internal/error.mjs +69 -0
  73. package/dist/internal/error.mjs.map +1 -0
  74. package/dist/internal/http.mjs +89 -0
  75. package/dist/internal/http.mjs.map +1 -0
  76. package/dist/internal/ids.mjs +8 -0
  77. package/dist/internal/ids.mjs.map +1 -0
  78. package/dist/internal/oauth.mjs +113 -0
  79. package/dist/internal/oauth.mjs.map +1 -0
  80. package/dist/internal/planner.mjs +88 -0
  81. package/dist/internal/planner.mjs.map +1 -0
  82. package/dist/internal/provider-async.mjs +83 -0
  83. package/dist/internal/provider-async.mjs.map +1 -0
  84. package/dist/internal/storage-memory.mjs +302 -0
  85. package/dist/internal/storage-memory.mjs.map +1 -0
  86. package/dist/internal/vercel/client.mjs +122 -0
  87. package/dist/internal/vercel/client.mjs.map +1 -0
  88. package/dist/internal/vercel/protocol.mjs +80 -0
  89. package/dist/internal/vercel/protocol.mjs.map +1 -0
  90. package/dist/internal/vercel/records.mjs +151 -0
  91. package/dist/internal/vercel/records.mjs.map +1 -0
  92. package/dist/package.mjs +6 -0
  93. package/dist/package.mjs.map +1 -0
  94. package/dist/types/Approval.d.ts +26 -0
  95. package/dist/types/Cleanup.d.ts +41 -0
  96. package/dist/types/Cloudflare.d.ts +36 -0
  97. package/dist/types/Connect.d.ts +506 -0
  98. package/dist/types/Custody.d.ts +38 -0
  99. package/dist/types/DnsRecord.d.ts +158 -0
  100. package/dist/types/DomainKit.d.ts +31 -0
  101. package/dist/types/DomainName.d.ts +27 -0
  102. package/dist/types/Plan.d.ts +76 -0
  103. package/dist/types/Principal.d.ts +18 -0
  104. package/dist/types/Provider.d.ts +248 -0
  105. package/dist/types/Providers.d.ts +16 -0
  106. package/dist/types/Provision.d.ts +75 -0
  107. package/dist/types/Reason.d.ts +118 -0
  108. package/dist/types/Receipt.d.ts +55 -0
  109. package/dist/types/Resolver.d.ts +135 -0
  110. package/dist/types/Server.d.ts +6523 -0
  111. package/dist/types/Storage.d.ts +314 -0
  112. package/dist/types/Testing.d.ts +84 -0
  113. package/dist/types/Transport.d.ts +124 -0
  114. package/dist/types/Vercel.d.ts +25 -0
  115. package/dist/types/Verify.d.ts +109 -0
  116. package/dist/types/entry/client.d.ts +2 -0
  117. package/dist/types/entry/server.d.ts +2 -0
  118. package/dist/types/entry/testing.d.ts +3 -0
  119. package/dist/types/index.d.ts +21 -0
  120. package/dist/types/internal/aes.d.ts +8 -0
  121. package/dist/types/internal/attempts.d.ts +40 -0
  122. package/dist/types/internal/cloudflare/client.d.ts +23 -0
  123. package/dist/types/internal/cloudflare/protocol.d.ts +71 -0
  124. package/dist/types/internal/cloudflare/records.d.ts +8 -0
  125. package/dist/types/internal/config.d.ts +4 -0
  126. package/dist/types/internal/conformance/provider.d.ts +12 -0
  127. package/dist/types/internal/conformance/storage.d.ts +14 -0
  128. package/dist/types/internal/digest.d.ts +10 -0
  129. package/dist/types/internal/doh.d.ts +19 -0
  130. package/dist/types/internal/error.d.ts +24 -0
  131. package/dist/types/internal/http.d.ts +27 -0
  132. package/dist/types/internal/ids.d.ts +3 -0
  133. package/dist/types/internal/oauth.d.ts +58 -0
  134. package/dist/types/internal/planner.d.ts +22 -0
  135. package/dist/types/internal/provider-async.d.ts +78 -0
  136. package/dist/types/internal/storage-memory.d.ts +2 -0
  137. package/dist/types/internal/vercel/client.d.ts +29 -0
  138. package/dist/types/internal/vercel/protocol.d.ts +124 -0
  139. package/dist/types/internal/vercel/records.d.ts +7 -0
  140. package/package.json +88 -12
  141. package/dist/canonical-json-CQYKsZw7.mjs +0 -310
  142. package/dist/canonical-json-CQYKsZw7.mjs.map +0 -1
  143. package/dist/connect-D-X8T9W8.d.mts +0 -1597
  144. package/dist/connect-oI8hcMxc.mjs +0 -185
  145. package/dist/connect-oI8hcMxc.mjs.map +0 -1
  146. package/dist/connection-Dp6ZdSo8.d.mts +0 -186
  147. package/dist/connection-api-lqG1nBtD.d.mts +0 -26
  148. package/dist/index.d.mts +0 -1551
  149. package/dist/managed-dns-connections-C3A_GBaz.mjs +0 -99
  150. package/dist/managed-dns-connections-C3A_GBaz.mjs.map +0 -1
  151. package/dist/promise.d.mts +0 -378
  152. package/dist/promise.mjs +0 -598
  153. package/dist/promise.mjs.map +0 -1
  154. package/dist/resolver-BeGz1ukw.mjs +0 -921
  155. package/dist/resolver-BeGz1ukw.mjs.map +0 -1
  156. package/dist/resolver-xoSwyXDJ.d.mts +0 -3734
  157. package/dist/server.d.mts +0 -128
  158. package/dist/server.mjs +0 -319
  159. package/dist/server.mjs.map +0 -1
  160. package/dist/testing.d.mts +0 -62
  161. package/dist/testing.mjs +0 -507
  162. package/dist/testing.mjs.map +0 -1
  163. package/dist/verify-DvGPb5C9.mjs +0 -2402
  164. package/dist/verify-DvGPb5C9.mjs.map +0 -1
  165. package/dist/verify-FpunAcM7.d.mts +0 -1335
@@ -0,0 +1,130 @@
1
+ import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
2
+ import { candidates } from "./DomainName.mjs";
3
+ import { InvalidInput, Unsupported } from "./Reason.mjs";
4
+ import { DomainKitError, decode, fail } from "./internal/error.mjs";
5
+ import { fromAsync } from "./internal/provider-async.mjs";
6
+ import { Data, Effect, Redacted, Schema } from "effect";
7
+ //#region src/Provider.ts
8
+ /**
9
+ * One declarative definition per DNS provider. Auth methods are optional cases, so a provider that
10
+ * only issues personal tokens declares `token` and nothing else. The definition drives connection
11
+ * routes, refresh, the UI method catalog, and rebuilding a session from stored context.
12
+ */
13
+ var Provider_exports = /* @__PURE__ */ __exportAll({
14
+ Envelope: () => Envelope,
15
+ Resolution: () => Resolution,
16
+ decodeContext: () => decodeContext,
17
+ describeMethods: () => describeMethods,
18
+ encodeContext: () => encodeContext,
19
+ fromAsync: () => fromAsync,
20
+ make: () => make,
21
+ methods: () => methods,
22
+ resolveAmong: () => resolveAmong,
23
+ tokenAuth: () => tokenAuth,
24
+ tokenFields: () => tokenFields
25
+ });
26
+ const Resolution = Data.taggedEnum();
27
+ const tokenAuth = (input) => ({
28
+ label: input.label,
29
+ ...input.docsUrl === void 0 ? {} : { docsUrl: input.docsUrl },
30
+ requiredCapabilities: input.requiredCapabilities,
31
+ fields: input.fields,
32
+ authenticate: (values) => decodeTokenValues(input.fields, values).pipe(Effect.flatMap(input.authenticate))
33
+ });
34
+ const decodeTokenValues = (fields, values) => {
35
+ const raw = {};
36
+ for (const [name, value] of Object.entries(values)) if (value !== void 0) raw[name] = Redacted.value(value);
37
+ return decode(fields, raw, "method");
38
+ };
39
+ /** Field descriptors derived from `TokenAuth.fields`: optional keys are not required, `Redacted` values are secret. */
40
+ const tokenFields = (auth) => Object.entries(auth.fields.fields).map(([name, field]) => {
41
+ const ast = field.ast;
42
+ return {
43
+ name,
44
+ required: ast.context?.isOptional !== true,
45
+ secret: ast._tag === "Declaration"
46
+ };
47
+ });
48
+ const describeMethods = (definition) => {
49
+ const found = [];
50
+ if (definition.auth.token !== void 0) found.push({
51
+ kind: "token",
52
+ label: definition.auth.token.label,
53
+ docsUrl: definition.auth.token.docsUrl ?? null,
54
+ fields: tokenFields(definition.auth.token)
55
+ });
56
+ if (definition.auth.oauth !== void 0) found.push({
57
+ kind: "oauth",
58
+ label: definition.auth.oauth.label,
59
+ docsUrl: null,
60
+ fields: null
61
+ });
62
+ if (definition.auth.integration !== void 0) found.push({
63
+ kind: "integration",
64
+ label: definition.auth.integration.label,
65
+ docsUrl: null,
66
+ fields: null
67
+ });
68
+ return found;
69
+ };
70
+ /** How provider context is persisted: the definition's version tag beside the encoded value. */
71
+ const Envelope = Schema.Struct({
72
+ version: Schema.String,
73
+ value: Schema.Unknown
74
+ });
75
+ /** Wrap a context value for storage; a value the schema rejects fails `InvalidInput`. */
76
+ const encodeContext = (definition, value) => Schema.encodeEffect(definition.context)(value).pipe(Effect.map((encoded) => ({
77
+ version: definition.contextVersion,
78
+ value: encoded
79
+ })), Effect.mapError((cause) => new DomainKitError({ reason: new InvalidInput({
80
+ message: cause.message,
81
+ field: "context"
82
+ }) })));
83
+ /** Validates a definition: an id and at least one auth method. Throws `DomainKit.Error` otherwise. */
84
+ const make = (definition) => {
85
+ const invalid = (message, field) => new DomainKitError({ reason: new InvalidInput({
86
+ message,
87
+ field
88
+ }) });
89
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(definition.id)) throw invalid(`Provider id ${JSON.stringify(definition.id)} must be lowercase kebab-case`, "id");
90
+ if (methods(definition).length === 0) throw invalid(`Provider ${definition.id} declares no auth method`, "auth");
91
+ if (definition.contextVersion.length === 0) throw invalid(`Provider ${definition.id} needs a contextVersion`, "contextVersion");
92
+ return definition;
93
+ };
94
+ const methods = (definition) => {
95
+ const found = [];
96
+ if (definition.auth.token !== void 0) found.push("token");
97
+ if (definition.auth.oauth !== void 0) found.push("oauth");
98
+ if (definition.auth.integration !== void 0) found.push("integration");
99
+ return found;
100
+ };
101
+ /**
102
+ * Pick the most specific zone among `targets` for `domain`: exactly one match resolves, several
103
+ * matches at the same depth need a selection, none is `NotFound`.
104
+ */
105
+ const resolveAmong = (domain, targets) => {
106
+ for (const candidate of candidates(domain)) {
107
+ const matches = targets.filter((target) => target.zone === candidate);
108
+ const [only] = matches;
109
+ if (matches.length === 1 && only !== void 0) return Resolution.Resolved({ target: only });
110
+ if (matches.length > 1) return Resolution.SelectionRequired({ candidates: matches });
111
+ }
112
+ return Resolution.NotFound();
113
+ };
114
+ /**
115
+ * Decode a stored envelope: the current version decodes with `context`, an older one goes
116
+ * through `migrateContext`, anything else fails `Unsupported`.
117
+ */
118
+ const decodeContext = (definition, stored) => decode(Envelope, stored, "context").pipe(Effect.flatMap((envelope) => {
119
+ if (envelope.version === definition.contextVersion) return decode(definition.context, envelope.value, "context");
120
+ if (definition.migrateContext !== void 0) return definition.migrateContext(envelope);
121
+ return fail(new Unsupported({
122
+ provider: definition.id,
123
+ operation: "context",
124
+ message: `${definition.id} context version ${envelope.version} is not supported (current ${definition.contextVersion})`
125
+ }));
126
+ }));
127
+ //#endregion
128
+ export { Envelope, Provider_exports, Resolution, decodeContext, describeMethods, encodeContext, fromAsync, make, methods, resolveAmong, tokenAuth, tokenFields };
129
+
130
+ //# sourceMappingURL=Provider.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Provider.mjs","names":["Errors.decode","Errors.DomainKitError","Reason.InvalidInput","DomainName.candidates","Errors.fail","Reason.Unsupported"],"sources":["../src/Provider.ts"],"sourcesContent":["/**\n * One declarative definition per DNS provider. Auth methods are optional cases, so a provider that\n * only issues personal tokens declares `token` and nothing else. The definition drives connection\n * routes, refresh, the UI method catalog, and rebuilding a session from stored context.\n */\nimport { Data, type DateTime, Effect, Redacted, Schema } from \"effect\";\n\nimport type * as DnsRecord from \"./DnsRecord.ts\";\nimport * as Errors from \"./internal/error.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as DomainName from \"./DomainName.ts\";\nimport type { Capability } from \"./Storage.ts\";\n\nexport type Fx<A> = Effect.Effect<A, Errors.DomainKitError>;\n\n/** A record as the provider reports it, with the id needed to read or delete it later. */\nexport interface ObservedRecord {\n readonly record: DnsRecord.Observed;\n readonly providerRecordId: string | null;\n}\n\n/** What a session can do inside one zone. The only thing planning and applying touch. */\nexport interface Dns {\n readonly list: (zone: string) => Fx<ReadonlyArray<ObservedRecord>>;\n /** Must return the provider's id for the new record; cleanup deletes by that id. */\n readonly create: (\n zone: string,\n record: DnsRecord.Model,\n ) => Fx<{ readonly providerRecordId: string }>;\n readonly get: (zone: string, providerRecordId: string) => Fx<DnsRecord.Observed | null>;\n readonly delete: (zone: string, providerRecordId: string) => Fx<void>;\n}\n\n/** A zone the credential can reach, with the provider identity needed to address it later. */\nexport interface Target {\n readonly zone: string;\n readonly context: unknown;\n readonly label: string;\n /** The zone's nameservers as the provider reports them; `Connect.discover` matches on them. */\n readonly nameservers?: ReadonlyArray<string>;\n}\n\nexport type Resolution = Data.TaggedEnum<{\n Resolved: { readonly target: Target };\n SelectionRequired: { readonly candidates: ReadonlyArray<Target> };\n NotFound: {};\n}>;\nexport const Resolution = Data.taggedEnum<Resolution>();\n\n/** A credential bound to provider account context. */\nexport interface Session {\n readonly listTargets: () => Fx<ReadonlyArray<Target>>;\n readonly resolveTarget: (domain: string) => Fx<Resolution>;\n readonly dns: (target: Target) => Dns;\n /** Capabilities actually held; promoted onto the authorization after first use. */\n readonly capabilities: () => Fx<ReadonlyArray<Capability>>;\n}\n\n/**\n * Secret material plus the account context needed to rebuild a session. `secret` is opaque to\n * DomainKit: a provider may pack an access token and a refresh token into it.\n */\nexport interface Credential {\n readonly secret: Redacted.Redacted<string>;\n readonly context: unknown;\n}\n\n/** A credential the provider just issued or refreshed; `null` expiry means it does not expire. */\nexport interface IssuedCredential extends Credential {\n readonly expiresAt: DateTime.Utc | null;\n}\n\n/** Raw token-method input keyed by the provider's declared field names. */\nexport type TokenValues = Readonly<Record<string, Redacted.Redacted<string> | undefined>>;\n\nexport interface TokenAuth {\n readonly label: string;\n /** Where the customer creates the token; shown in the UI. */\n readonly docsUrl?: string;\n readonly requiredCapabilities: ReadonlyArray<Capability>;\n /**\n * The values this method needs, declared once so forms render from it: secrets as\n * `Schema.RedactedFromValue(Schema.String)`, optional ones through `Schema.optionalKey`.\n */\n readonly fields: Schema.Struct<Schema.Struct.Fields>;\n /** Validate the values (already decoded with `fields`) and return account context. */\n readonly authenticate: (values: TokenValues) => Fx<IssuedCredential>;\n}\n\n/** Build a `TokenAuth` whose `authenticate` receives the record decoded by `fields`. */\n/** Field schemas whose decoding needs no services, so `authenticate` stays a plain `Fx`. */\nexport type TokenFieldSchemas = { readonly [name: string]: Schema.ConstraintDecoder<unknown> };\n\nexport const tokenAuth = <const Fields extends TokenFieldSchemas>(input: {\n readonly label: string;\n readonly docsUrl?: string;\n readonly requiredCapabilities: ReadonlyArray<Capability>;\n readonly fields: Schema.Struct<Fields>;\n readonly authenticate: (values: Schema.Struct<Fields>[\"Type\"]) => Fx<IssuedCredential>;\n}): TokenAuth => ({\n label: input.label,\n ...(input.docsUrl === undefined ? {} : { docsUrl: input.docsUrl }),\n requiredCapabilities: input.requiredCapabilities,\n fields: input.fields as Schema.Struct<Schema.Struct.Fields>,\n authenticate: (values) =>\n decodeTokenValues(input.fields, values).pipe(Effect.flatMap(input.authenticate)),\n});\n\nconst decodeTokenValues = <Fields extends TokenFieldSchemas>(\n fields: Schema.Struct<Fields>,\n values: TokenValues,\n): Effect.Effect<Schema.Struct<Fields>[\"Type\"], Errors.DomainKitError> => {\n const raw: Record<string, string> = {};\n for (const [name, value] of Object.entries(values)) {\n if (value !== undefined) raw[name] = Redacted.value(value);\n }\n return Errors.decode(fields, raw, \"method\");\n};\n\nexport interface FieldDescriptor {\n readonly name: string;\n readonly required: boolean;\n readonly secret: boolean;\n}\n\n/** What a UI needs to render one auth method; `fields` is `null` for interactive methods. */\nexport interface MethodDescriptor {\n readonly kind: AuthMethod;\n readonly label: string;\n readonly docsUrl: string | null;\n readonly fields: ReadonlyArray<FieldDescriptor> | null;\n}\n\n/** Field descriptors derived from `TokenAuth.fields`: optional keys are not required, `Redacted` values are secret. */\nexport const tokenFields = (auth: TokenAuth): ReadonlyArray<FieldDescriptor> =>\n Object.entries(auth.fields.fields).map(([name, field]) => {\n const ast = (field as { readonly ast: FieldAst }).ast;\n return {\n name,\n required: ast.context?.isOptional !== true,\n secret: ast._tag === \"Declaration\",\n };\n });\n\ninterface FieldAst {\n readonly _tag: string;\n readonly context?: { readonly isOptional?: boolean } | undefined;\n}\n\nexport const describeMethods = (definition: Definition): ReadonlyArray<MethodDescriptor> => {\n const found: Array<MethodDescriptor> = [];\n if (definition.auth.token !== undefined) {\n found.push({\n kind: \"token\",\n label: definition.auth.token.label,\n docsUrl: definition.auth.token.docsUrl ?? null,\n fields: tokenFields(definition.auth.token),\n });\n }\n if (definition.auth.oauth !== undefined) {\n found.push({ kind: \"oauth\", label: definition.auth.oauth.label, docsUrl: null, fields: null });\n }\n if (definition.auth.integration !== undefined) {\n found.push({\n kind: \"integration\",\n label: definition.auth.integration.label,\n docsUrl: null,\n fields: null,\n });\n }\n return found;\n};\n\nexport interface CallbackInput {\n readonly code: string;\n /** The redirect URI registered at start; token exchanges echo it. */\n readonly callbackUrl: string;\n /** Every query parameter the provider appended to the callback. */\n readonly params: Readonly<Record<string, string>>;\n}\n\nexport interface OAuthAuth {\n readonly label: string;\n readonly scopes: ReadonlyArray<string>;\n readonly start: (input: {\n readonly state: string;\n readonly callbackUrl: string;\n readonly codeChallenge: string;\n }) => Fx<{ readonly authorizationUrl: string }>;\n readonly complete: (\n input: CallbackInput & { readonly codeVerifier: string },\n ) => Fx<IssuedCredential>;\n readonly refresh: (credential: Credential) => Fx<IssuedCredential>;\n readonly revoke?: (credential: Credential) => Fx<void>;\n}\n\n/** Marketplace-style installs (Vercel integrations) that redirect but are not OAuth. */\nexport interface IntegrationAuth {\n readonly label: string;\n readonly start: (input: {\n readonly state: string;\n readonly callbackUrl: string;\n }) => Fx<{ readonly authorizationUrl: string }>;\n readonly complete: (input: CallbackInput) => Fx<IssuedCredential>;\n readonly refresh?: (credential: Credential) => Fx<IssuedCredential>;\n readonly revoke?: (credential: Credential) => Fx<void>;\n}\n\nexport interface Definition<Context = unknown> {\n readonly id: string;\n readonly name: string;\n readonly auth: {\n readonly token?: TokenAuth;\n readonly oauth?: OAuthAuth;\n readonly integration?: IntegrationAuth;\n };\n /** Schema for the account/zone context stored on authorizations and attachments. */\n readonly context: Schema.Codec<Context, unknown, never, never>;\n /** Tag written into every stored context envelope, e.g. `cloudflare.v1`; bump it with the schema. */\n readonly contextVersion: string;\n /** Upgrade an envelope written under an older `contextVersion`; absent means `Unsupported`. */\n readonly migrateContext?: (envelope: Envelope) => Fx<Context>;\n readonly session: (credential: Credential) => Session;\n}\n\n/** How provider context is persisted: the definition's version tag beside the encoded value. */\nexport const Envelope = Schema.Struct({ version: Schema.String, value: Schema.Unknown });\nexport type Envelope = typeof Envelope.Type;\n\n/** Wrap a context value for storage; a value the schema rejects fails `InvalidInput`. */\nexport const encodeContext = <Context>(\n definition: Definition<Context>,\n value: Context,\n): Effect.Effect<Envelope, Errors.DomainKitError> =>\n Schema.encodeEffect(definition.context)(value).pipe(\n Effect.map((encoded) => ({ version: definition.contextVersion, value: encoded })),\n Effect.mapError(\n (cause) =>\n new Errors.DomainKitError({\n reason: new Reason.InvalidInput({ message: cause.message, field: \"context\" }),\n }),\n ),\n );\n\nexport type AuthMethod = \"token\" | \"oauth\" | \"integration\";\n\n/** Validates a definition: an id and at least one auth method. Throws `DomainKit.Error` otherwise. */\nexport const make = <Context>(definition: Definition<Context>): Definition<Context> => {\n const invalid = (message: string, field: string) =>\n new Errors.DomainKitError({\n reason: new Reason.InvalidInput({ message, field }),\n });\n if (!/^[a-z0-9][a-z0-9-]*$/.test(definition.id)) {\n throw invalid(\n `Provider id ${JSON.stringify(definition.id)} must be lowercase kebab-case`,\n \"id\",\n );\n }\n if (methods(definition).length === 0) {\n throw invalid(`Provider ${definition.id} declares no auth method`, \"auth\");\n }\n if (definition.contextVersion.length === 0) {\n throw invalid(`Provider ${definition.id} needs a contextVersion`, \"contextVersion\");\n }\n return definition;\n};\n\nexport const methods = (definition: Definition): ReadonlyArray<AuthMethod> => {\n const found: Array<AuthMethod> = [];\n if (definition.auth.token !== undefined) found.push(\"token\");\n if (definition.auth.oauth !== undefined) found.push(\"oauth\");\n if (definition.auth.integration !== undefined) found.push(\"integration\");\n return found;\n};\n\n/**\n * Pick the most specific zone among `targets` for `domain`: exactly one match resolves, several\n * matches at the same depth need a selection, none is `NotFound`.\n */\nexport const resolveAmong = (\n domain: DomainName.Model,\n targets: ReadonlyArray<Target>,\n): Resolution => {\n for (const candidate of DomainName.candidates(domain)) {\n const matches = targets.filter((target) => target.zone === candidate);\n const [only] = matches;\n if (matches.length === 1 && only !== undefined) return Resolution.Resolved({ target: only });\n if (matches.length > 1) return Resolution.SelectionRequired({ candidates: matches });\n }\n return Resolution.NotFound();\n};\n\n/**\n * Decode a stored envelope: the current version decodes with `context`, an older one goes\n * through `migrateContext`, anything else fails `Unsupported`.\n */\nexport const decodeContext = <Context>(\n definition: Definition<Context>,\n stored: unknown,\n): Effect.Effect<Context, Errors.DomainKitError> =>\n Errors.decode(Envelope, stored, \"context\").pipe(\n Effect.flatMap((envelope) => {\n if (envelope.version === definition.contextVersion) {\n return Errors.decode(definition.context, envelope.value, \"context\");\n }\n if (definition.migrateContext !== undefined) return definition.migrateContext(envelope);\n return Errors.fail(\n new Reason.Unsupported({\n provider: definition.id,\n operation: \"context\",\n message: `${definition.id} context version ${envelope.version} is not supported (current ${definition.contextVersion})`,\n }),\n );\n }),\n );\n\nexport {\n type AsyncCredential,\n type AsyncDefinition,\n type AsyncDns,\n type AsyncIntegrationAuth,\n type AsyncIssuedCredential,\n type AsyncOAuthAuth,\n type AsyncSession,\n type AsyncTokenAuth,\n fromAsync,\n} from \"./internal/provider-async.ts\";\n"],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
2
+ import { InvalidInput, NotFound } from "./Reason.mjs";
3
+ import { DomainKitError, fail } from "./internal/error.mjs";
4
+ import { methods } from "./Provider.mjs";
5
+ import { Context, Effect, Layer } from "effect";
6
+ //#region src/Providers.ts
7
+ /** The registry of provider definitions a host enabled. Built by `DomainKit.layer`. */
8
+ var Providers_exports = /* @__PURE__ */ __exportAll({
9
+ Service: () => Service,
10
+ layer: () => layer,
11
+ make: () => make
12
+ });
13
+ var Service = class extends Context.Service()("@domainkit/Providers") {};
14
+ const make = (definitions) => {
15
+ const byId = /* @__PURE__ */ new Map();
16
+ for (const definition of definitions) {
17
+ if (byId.has(definition.id)) throw new DomainKitError({ reason: new InvalidInput({
18
+ message: `Provider ${definition.id} is registered twice`,
19
+ field: "providers"
20
+ }) });
21
+ byId.set(definition.id, definition);
22
+ }
23
+ const get = (id) => Effect.suspend(() => {
24
+ const definition = byId.get(id);
25
+ return definition === void 0 ? fail(new NotFound({
26
+ entity: "provider",
27
+ id
28
+ })) : Effect.succeed(definition);
29
+ });
30
+ return {
31
+ get,
32
+ list: () => [...byId.values()],
33
+ methods: (id) => Effect.map(get(id), methods)
34
+ };
35
+ };
36
+ const layer = (definitions) => Layer.sync(Service)(() => make(definitions));
37
+ //#endregion
38
+ export { Providers_exports, Service, layer, make };
39
+
40
+ //# sourceMappingURL=Providers.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Providers.mjs","names":["Errors.DomainKitError","Reason.InvalidInput","Errors.fail","Reason.NotFound","Provider.methods"],"sources":["../src/Providers.ts"],"sourcesContent":["/** The registry of provider definitions a host enabled. Built by `DomainKit.layer`. */\nimport { Context, Effect, Layer } from \"effect\";\n\nimport * as Errors from \"./internal/error.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as Provider from \"./Provider.ts\";\n\nexport interface Interface {\n readonly get: (id: string) => Effect.Effect<Provider.Definition, Errors.DomainKitError>;\n readonly list: () => ReadonlyArray<Provider.Definition>;\n /** For UI catalogs: which methods each provider offers. */\n readonly methods: (\n id: string,\n ) => Effect.Effect<ReadonlyArray<Provider.AuthMethod>, Errors.DomainKitError>;\n}\n\nexport class Service extends Context.Service<Service, Interface>()(\"@domainkit/Providers\") {}\n\nexport const make = (definitions: ReadonlyArray<Provider.Definition>): Interface => {\n const byId = new Map<string, Provider.Definition>();\n for (const definition of definitions) {\n if (byId.has(definition.id)) {\n throw new Errors.DomainKitError({\n reason: new Reason.InvalidInput({\n message: `Provider ${definition.id} is registered twice`,\n field: \"providers\",\n }),\n });\n }\n byId.set(definition.id, definition);\n }\n const get: Interface[\"get\"] = (id) =>\n Effect.suspend(() => {\n const definition = byId.get(id);\n return definition === undefined\n ? Errors.fail(new Reason.NotFound({ entity: \"provider\", id }))\n : Effect.succeed(definition);\n });\n return {\n get,\n list: () => [...byId.values()],\n methods: (id) => Effect.map(get(id), Provider.methods),\n };\n};\n\nexport const layer = (definitions: ReadonlyArray<Provider.Definition>): Layer.Layer<Service> =>\n Layer.sync(Service)(() => make(definitions));\n"],"mappings":";;;;;;;;;;;;AAgBA,IAAa,UAAb,cAA6B,QAAQ,QAA4B,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC;AAE5F,MAAa,QAAQ,gBAA+D;CAClF,MAAM,uBAAO,IAAI,IAAiC;CAClD,KAAK,MAAM,cAAc,aAAa;EACpC,IAAI,KAAK,IAAI,WAAW,EAAE,GACxB,MAAM,IAAIA,eAAsB,EAC9B,QAAQ,IAAIC,aAAoB;GAC9B,SAAS,YAAY,WAAW,GAAG;GACnC,OAAO;EACT,CAAC,EACH,CAAC;EAEH,KAAK,IAAI,WAAW,IAAI,UAAU;CACpC;CACA,MAAM,OAAyB,OAC7B,OAAO,cAAc;EACnB,MAAM,aAAa,KAAK,IAAI,EAAE;EAC9B,OAAO,eAAe,KAAA,IAClBC,KAAY,IAAIC,SAAgB;GAAE,QAAQ;GAAY;EAAG,CAAC,CAAC,IAC3D,OAAO,QAAQ,UAAU;CAC/B,CAAC;CACH,OAAO;EACL;EACA,YAAY,CAAC,GAAG,KAAK,OAAO,CAAC;EAC7B,UAAU,OAAO,OAAO,IAAI,IAAI,EAAE,GAAGC,OAAgB;CACvD;AACF;AAEA,MAAa,SAAS,gBACpB,MAAM,KAAK,OAAO,CAAC,OAAO,KAAK,WAAW,CAAC"}
@@ -0,0 +1,93 @@
1
+ import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
2
+ import { decode } from "./DomainName.mjs";
3
+ import { NotFound } from "./Reason.mjs";
4
+ import { fail } from "./internal/error.mjs";
5
+ import { Service as Service$1 } from "./Storage.mjs";
6
+ import { Service as Service$2 } from "./Connect.mjs";
7
+ import { reconcile } from "./internal/planner.mjs";
8
+ import { assertWithin, make as make$1 } from "./internal/attempts.mjs";
9
+ import { Context, Effect, Layer, Option } from "effect";
10
+ //#region src/Provision.ts
11
+ /**
12
+ * plan -> approve -> apply, durable. Every step is a stored attempt, so a host can render the
13
+ * plan in one request, collect consent in another, and apply in a third, and a retry of any step
14
+ * is idempotent.
15
+ */
16
+ var Provision_exports = /* @__PURE__ */ __exportAll({
17
+ Policy: () => Policy,
18
+ Service: () => Service,
19
+ apply: () => apply,
20
+ approve: () => approve,
21
+ defaults: () => defaults,
22
+ get: () => get,
23
+ latest: () => latest,
24
+ layer: () => layer,
25
+ make: () => make,
26
+ plan: () => plan,
27
+ reject: () => reject
28
+ });
29
+ var Service = class extends Context.Service()("@domainkit/Provision") {};
30
+ const defaults = {
31
+ planTtlMs: 36e5,
32
+ applyLeaseMs: 12e4
33
+ };
34
+ var Policy = class extends Context.Reference("@domainkit/Provision/Policy", { defaultValue: () => defaults }) {};
35
+ const make = Effect.gen(function* () {
36
+ const storage = yield* Service$1;
37
+ const connect = yield* Service$2;
38
+ const attempts = make$1(storage, connect, "provisioning");
39
+ const attachmentFor = (input) => Effect.gen(function* () {
40
+ const domain = yield* decode(input);
41
+ const attachment = yield* storage.attachments.byDomain(domain);
42
+ if (Option.isNone(attachment)) return yield* fail(new NotFound({
43
+ entity: "attachment",
44
+ id: domain
45
+ }));
46
+ return attachment.value;
47
+ });
48
+ const view = (attempt) => ({
49
+ plan: attempt.plan,
50
+ status: attempt.status,
51
+ approval: attempt.approval,
52
+ receipt: attempt.receipt,
53
+ rejection: attempt.rejection
54
+ });
55
+ return {
56
+ plan: (input) => Effect.gen(function* () {
57
+ const policy = yield* Policy;
58
+ const attachment = yield* attachmentFor(input.domain);
59
+ yield* assertWithin(attachment, input.requirements);
60
+ const { session, target } = yield* connect.session(attachment.id);
61
+ const observed = yield* session.dns(target).list(target.zone);
62
+ const operations = yield* reconcile(input.requirements, observed.map(({ record }) => record));
63
+ return yield* attempts.record({
64
+ attachment,
65
+ target,
66
+ operations,
67
+ ttlMs: policy.planTtlMs,
68
+ sourceReceiptId: null
69
+ });
70
+ }),
71
+ approve: (plan, options) => attempts.approve(plan, options),
72
+ reject: (plan, options) => Effect.map(attempts.reject(plan, options), view),
73
+ apply: (approval) => Effect.flatMap(Policy, (policy) => attempts.apply(approval, policy)),
74
+ get: (planId) => Effect.map(attempts.attemptOf(planId), view),
75
+ latest: (domain) => Effect.gen(function* () {
76
+ const attachment = yield* attachmentFor(domain);
77
+ const latest = yield* storage.attempts.latest(attachment.id, "provisioning");
78
+ return Option.isSome(latest) ? view(latest.value) : null;
79
+ })
80
+ };
81
+ });
82
+ const layer = Layer.effect(Service)(make);
83
+ const accessor = (pick) => (...args) => Effect.flatMap(Service, (service) => pick(service)(...args));
84
+ const plan = accessor((service) => service.plan);
85
+ const approve = accessor((service) => service.approve);
86
+ const reject = accessor((service) => service.reject);
87
+ const apply = accessor((service) => service.apply);
88
+ const get = accessor((service) => service.get);
89
+ const latest = accessor((service) => service.latest);
90
+ //#endregion
91
+ export { Policy, Provision_exports, Service, apply, approve, defaults, get, latest, layer, make, plan, reject };
92
+
93
+ //# sourceMappingURL=Provision.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Provision.mjs","names":["Storage.Service","Connect.Service","Attempts.make","DomainName.decode","Errors.fail","Reason.NotFound","Attempts.assertWithin","Planner.reconcile"],"sources":["../src/Provision.ts"],"sourcesContent":["/**\n * plan -> approve -> apply, durable. Every step is a stored attempt, so a host can render the\n * plan in one request, collect consent in another, and apply in a third, and a retry of any step\n * is idempotent.\n */\nimport { Context, Effect, Layer, Option } from \"effect\";\n\nimport type * as Approval from \"./Approval.ts\";\nimport * as Connect from \"./Connect.ts\";\nimport type * as DnsRecord from \"./DnsRecord.ts\";\nimport * as Errors from \"./internal/error.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as DomainName from \"./DomainName.ts\";\nimport * as Attempts from \"./internal/attempts.ts\";\nimport * as Planner from \"./internal/planner.ts\";\nimport type * as Plan from \"./Plan.ts\";\nimport type * as Principal from \"./Principal.ts\";\nimport type * as Receipt from \"./Receipt.ts\";\nimport * as Storage from \"./Storage.ts\";\n\ntype Fx<A> = Effect.Effect<A, Errors.DomainKitError, Principal.Service>;\n\nexport interface Attempt {\n readonly plan: Plan.Model;\n readonly status: Storage.AttemptStatus;\n readonly approval: Approval.Model | null;\n readonly receipt: Receipt.Model | null;\n readonly rejection: Storage.Rejection | null;\n}\n\nexport interface RejectOptions {\n readonly reason?: string;\n}\n\nexport interface Interface {\n /** Read provider state and build an additive plan. Fails `NotFound` when the domain is not attached. */\n readonly plan: (input: {\n readonly domain: string;\n readonly requirements: ReadonlyArray<DnsRecord.Model>;\n }) => Fx<Plan.Model>;\n /**\n * Record the principal's consent. Fails `Conflict` unless `allowPartial` and the selected\n * operations are conflict-free. Approving an already-approved plan returns the same approval.\n */\n readonly approve: (\n plan: Plan.Model | Plan.PlanId,\n options?: Attempts.ApproveOptions,\n ) => Fx<Approval.Model>;\n /**\n * Decline the plan for the acting principal; terminal. Rejecting again returns the same\n * attempt; a plan that was approved or applied fails `Stale`.\n */\n readonly reject: (plan: Plan.Model | Plan.PlanId, options?: RejectOptions) => Fx<Attempt>;\n /**\n * Re-plan the zone, fail `Stale` if the digest moved, then create records. Partial success is a\n * `partial` receipt. Applying an attempt that already completed returns its receipt.\n */\n readonly apply: (approval: Approval.Model | Approval.ApprovalId) => Fx<Receipt.Model>;\n readonly get: (planId: Plan.PlanId) => Fx<Attempt>;\n readonly latest: (domain: string) => Fx<Attempt | null>;\n}\n\nexport class Service extends Context.Service<Service, Interface>()(\"@domainkit/Provision\") {}\n\nexport interface PolicyShape {\n /** Plan lifetime before it must be rebuilt. Default 1 hour. */\n readonly planTtlMs: number;\n /** Apply lease; a crashed apply can be retried after this. Default 2 minutes. */\n readonly applyLeaseMs: number;\n}\nexport const defaults: PolicyShape = { planTtlMs: 60 * 60_000, applyLeaseMs: 2 * 60_000 };\nexport class Policy extends Context.Reference<PolicyShape>(\"@domainkit/Provision/Policy\", {\n defaultValue: () => defaults,\n}) {}\n\nexport const make: Effect.Effect<Interface, never, Storage.Service | Connect.Service> = Effect.gen(\n function* () {\n const storage = yield* Storage.Service;\n const connect = yield* Connect.Service;\n const attempts = Attempts.make(storage, connect, \"provisioning\");\n\n const attachmentFor = (input: string): Fx<Storage.Attachment> =>\n Effect.gen(function* () {\n const domain = yield* DomainName.decode(input);\n const attachment = yield* storage.attachments.byDomain(domain);\n if (Option.isNone(attachment)) {\n return yield* Errors.fail(new Reason.NotFound({ entity: \"attachment\", id: domain }));\n }\n return attachment.value;\n });\n\n const view = (attempt: Storage.Attempt): Attempt => ({\n plan: attempt.plan,\n status: attempt.status,\n approval: attempt.approval,\n receipt: attempt.receipt,\n rejection: attempt.rejection,\n });\n\n return {\n plan: (input) =>\n Effect.gen(function* () {\n const policy = yield* Policy;\n const attachment = yield* attachmentFor(input.domain);\n yield* Attempts.assertWithin(attachment, input.requirements);\n const { session, target } = yield* connect.session(attachment.id);\n const observed = yield* session.dns(target).list(target.zone);\n const operations = yield* Planner.reconcile(\n input.requirements,\n observed.map(({ record }) => record),\n );\n return yield* attempts.record({\n attachment,\n target,\n operations,\n ttlMs: policy.planTtlMs,\n sourceReceiptId: null,\n });\n }),\n approve: (plan, options) => attempts.approve(plan, options),\n reject: (plan, options) => Effect.map(attempts.reject(plan, options), view),\n apply: (approval) => Effect.flatMap(Policy, (policy) => attempts.apply(approval, policy)),\n get: (planId) => Effect.map(attempts.attemptOf(planId), view),\n latest: (domain) =>\n Effect.gen(function* () {\n const attachment = yield* attachmentFor(domain);\n const latest = yield* storage.attempts.latest(attachment.id, \"provisioning\");\n return Option.isSome(latest) ? view(latest.value) : null;\n }),\n };\n },\n);\n\nexport const layer: Layer.Layer<Service, never, Storage.Service | Connect.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 plan = accessor((service) => service.plan);\nexport const approve = accessor((service) => service.approve);\nexport const reject = accessor((service) => service.reject);\nexport const apply = accessor((service) => service.apply);\nexport const get = accessor((service) => service.get);\nexport const latest = accessor((service) => service.latest);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8DA,IAAa,UAAb,cAA6B,QAAQ,QAA4B,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC;AAQ5F,MAAa,WAAwB;CAAE,WAAW;CAAa,cAAc;AAAW;AACxF,IAAa,SAAb,cAA4B,QAAQ,UAAuB,+BAA+B,EACxF,oBAAoB,SACtB,CAAC,CAAC,CAAC,CAAC;AAEJ,MAAa,OAA2E,OAAO,IAC7F,aAAa;CACX,MAAM,UAAU,OAAOA;CACvB,MAAM,UAAU,OAAOC;CACvB,MAAM,WAAWC,OAAc,SAAS,SAAS,cAAc;CAE/D,MAAM,iBAAiB,UACrB,OAAO,IAAI,aAAa;EACtB,MAAM,SAAS,OAAOC,OAAkB,KAAK;EAC7C,MAAM,aAAa,OAAO,QAAQ,YAAY,SAAS,MAAM;EAC7D,IAAI,OAAO,OAAO,UAAU,GAC1B,OAAO,OAAOC,KAAY,IAAIC,SAAgB;GAAE,QAAQ;GAAc,IAAI;EAAO,CAAC,CAAC;EAErF,OAAO,WAAW;CACpB,CAAC;CAEH,MAAM,QAAQ,aAAuC;EACnD,MAAM,QAAQ;EACd,QAAQ,QAAQ;EAChB,UAAU,QAAQ;EAClB,SAAS,QAAQ;EACjB,WAAW,QAAQ;CACrB;CAEA,OAAO;EACL,OAAO,UACL,OAAO,IAAI,aAAa;GACtB,MAAM,SAAS,OAAO;GACtB,MAAM,aAAa,OAAO,cAAc,MAAM,MAAM;GACpD,OAAOC,aAAsB,YAAY,MAAM,YAAY;GAC3D,MAAM,EAAE,SAAS,WAAW,OAAO,QAAQ,QAAQ,WAAW,EAAE;GAChE,MAAM,WAAW,OAAO,QAAQ,IAAI,MAAM,CAAC,CAAC,KAAK,OAAO,IAAI;GAC5D,MAAM,aAAa,OAAOC,UACxB,MAAM,cACN,SAAS,KAAK,EAAE,aAAa,MAAM,CACrC;GACA,OAAO,OAAO,SAAS,OAAO;IAC5B;IACA;IACA;IACA,OAAO,OAAO;IACd,iBAAiB;GACnB,CAAC;EACH,CAAC;EACH,UAAU,MAAM,YAAY,SAAS,QAAQ,MAAM,OAAO;EAC1D,SAAS,MAAM,YAAY,OAAO,IAAI,SAAS,OAAO,MAAM,OAAO,GAAG,IAAI;EAC1E,QAAQ,aAAa,OAAO,QAAQ,SAAS,WAAW,SAAS,MAAM,UAAU,MAAM,CAAC;EACxF,MAAM,WAAW,OAAO,IAAI,SAAS,UAAU,MAAM,GAAG,IAAI;EAC5D,SAAS,WACP,OAAO,IAAI,aAAa;GACtB,MAAM,aAAa,OAAO,cAAc,MAAM;GAC9C,MAAM,SAAS,OAAO,QAAQ,SAAS,OAAO,WAAW,IAAI,cAAc;GAC3E,OAAO,OAAO,OAAO,MAAM,IAAI,KAAK,OAAO,KAAK,IAAI;EACtD,CAAC;CACL;AACF,CACF;AAEA,MAAa,QACX,MAAM,OAAO,OAAO,CAAC,CAAC,IAAI;AAE5B,MAAM,YAEF,UAED,GAAG,SACF,OAAO,QAAQ,UAAU,YAAY,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;AAE/D,MAAa,OAAO,UAAU,YAAY,QAAQ,IAAI;AACtD,MAAa,UAAU,UAAU,YAAY,QAAQ,OAAO;AAC5D,MAAa,SAAS,UAAU,YAAY,QAAQ,MAAM;AAC1D,MAAa,QAAQ,UAAU,YAAY,QAAQ,KAAK;AACxD,MAAa,MAAM,UAAU,YAAY,QAAQ,GAAG;AACpD,MAAa,SAAS,UAAU,YAAY,QAAQ,MAAM"}
@@ -0,0 +1,162 @@
1
+ import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
2
+ import { Conflict as Conflict$1, Digest as Digest$1, PlanId as PlanId$1 } from "./Plan.mjs";
3
+ import { Schema } from "effect";
4
+ //#region src/Reason.ts
5
+ /**
6
+ * Why an operation failed. Every `DomainKit.Error` carries exactly one of these as `reason`; hosts
7
+ * match on `reason._tag` and add a reason here rather than a new error class.
8
+ */
9
+ var Reason_exports = /* @__PURE__ */ __exportAll({
10
+ Busy: () => Busy,
11
+ Conflict: () => Conflict,
12
+ CryptoFailed: () => CryptoFailed,
13
+ Expired: () => Expired,
14
+ Forbidden: () => Forbidden,
15
+ InvalidInput: () => InvalidInput,
16
+ Model: () => Model,
17
+ NotFound: () => NotFound,
18
+ ProviderConflict: () => ProviderConflict,
19
+ ProviderRejected: () => ProviderRejected,
20
+ ProviderUnavailable: () => ProviderUnavailable,
21
+ Reconnect: () => Reconnect,
22
+ ResolverFailed: () => ResolverFailed,
23
+ Stale: () => Stale,
24
+ StorageFailed: () => StorageFailed,
25
+ Unauthenticated: () => Unauthenticated,
26
+ Unsupported: () => Unsupported
27
+ });
28
+ const PlanId = Schema.suspend(() => PlanId$1);
29
+ const Digest = Schema.suspend(() => Digest$1);
30
+ const Conflicts = Schema.suspend(() => Schema.Array(Conflict$1));
31
+ var InvalidInput = class extends Schema.TaggedError("@domainkit/Reason/InvalidInput")("InvalidInput", {
32
+ message: Schema.String,
33
+ field: Schema.optionalKey(Schema.String)
34
+ }) {};
35
+ var Unauthenticated = class extends Schema.TaggedError("@domainkit/Reason/Unauthenticated")("Unauthenticated", { message: Schema.String }) {};
36
+ var Forbidden = class extends Schema.TaggedError("@domainkit/Reason/Forbidden")("Forbidden", { message: Schema.String }) {};
37
+ var NotFound = class extends Schema.TaggedError("@domainkit/Reason/NotFound")("NotFound", {
38
+ entity: Schema.Literals([
39
+ "connection",
40
+ "attachment",
41
+ "plan",
42
+ "approval",
43
+ "receipt",
44
+ "continuation",
45
+ "provider",
46
+ "zone",
47
+ "authorization",
48
+ "record"
49
+ ]),
50
+ id: Schema.String
51
+ }) {
52
+ get message() {
53
+ return `${this.entity} ${this.id} was not found`;
54
+ }
55
+ };
56
+ /** The plan contains conflicts and cannot be applied. */
57
+ var Conflict = class extends Schema.TaggedError("@domainkit/Reason/Conflict")("Conflict", {
58
+ planId: PlanId,
59
+ operations: Conflicts
60
+ }) {
61
+ get message() {
62
+ return `plan ${this.planId} has ${this.operations.length} conflicting operation(s)`;
63
+ }
64
+ };
65
+ /** Provider state moved since the plan was built; build a new plan. */
66
+ var Stale = class extends Schema.TaggedError("@domainkit/Reason/Stale")("Stale", {
67
+ planId: PlanId,
68
+ digest: Digest
69
+ }) {
70
+ get message() {
71
+ return `plan ${this.planId} is stale; provider state no longer matches digest ${this.digest}`;
72
+ }
73
+ };
74
+ var Expired = class extends Schema.TaggedError("@domainkit/Reason/Expired")("Expired", {
75
+ entity: Schema.Literals([
76
+ "plan",
77
+ "approval",
78
+ "continuation",
79
+ "credential"
80
+ ]),
81
+ id: Schema.String
82
+ }) {
83
+ get message() {
84
+ return `${this.entity} ${this.id} has expired`;
85
+ }
86
+ };
87
+ /** Another operation holds the lock (e.g. concurrent apply or refresh). Retrying is safe. */
88
+ var Busy = class extends Schema.TaggedError("@domainkit/Reason/Busy")("Busy", { key: Schema.String }) {
89
+ get message() {
90
+ return `${this.key} is busy`;
91
+ }
92
+ };
93
+ var ProviderRejected = class extends Schema.TaggedError("@domainkit/Reason/ProviderRejected")("ProviderRejected", {
94
+ provider: Schema.String,
95
+ code: Schema.optionalKey(Schema.String),
96
+ message: Schema.String
97
+ }) {};
98
+ var ProviderUnavailable = class extends Schema.TaggedError("@domainkit/Reason/ProviderUnavailable")("ProviderUnavailable", {
99
+ provider: Schema.String,
100
+ retryAfterMs: Schema.optionalKey(Schema.Number),
101
+ message: Schema.String
102
+ }) {};
103
+ /** The provider credential no longer works; the customer must reconnect. */
104
+ var Reconnect = class extends Schema.TaggedError("@domainkit/Reason/Reconnect")("Reconnect", {
105
+ provider: Schema.String,
106
+ connectionId: Schema.String
107
+ }) {
108
+ get message() {
109
+ return `${this.provider} connection ${this.connectionId} must be reconnected`;
110
+ }
111
+ };
112
+ var StorageFailed = class extends Schema.TaggedError("@domainkit/Reason/StorageFailed")("StorageFailed", {
113
+ operation: Schema.String,
114
+ message: Schema.String
115
+ }) {};
116
+ var CryptoFailed = class extends Schema.TaggedError("@domainkit/Reason/CryptoFailed")("CryptoFailed", { operation: Schema.Literals([
117
+ "digest",
118
+ "seal",
119
+ "open"
120
+ ]) }) {
121
+ get message() {
122
+ return `crypto ${this.operation} failed`;
123
+ }
124
+ };
125
+ var ResolverFailed = class extends Schema.TaggedError("@domainkit/Reason/ResolverFailed")("ResolverFailed", {
126
+ resolver: Schema.String,
127
+ message: Schema.String
128
+ }) {};
129
+ /** The provider refused the write because a conflicting record already exists there. */
130
+ var ProviderConflict = class extends Schema.TaggedError("@domainkit/Reason/ProviderConflict")("ProviderConflict", {
131
+ provider: Schema.String,
132
+ code: Schema.optionalKey(Schema.String),
133
+ message: Schema.String
134
+ }) {};
135
+ /** The provider, or this target within it, cannot do the requested operation. */
136
+ var Unsupported = class extends Schema.TaggedError("@domainkit/Reason/Unsupported")("Unsupported", {
137
+ provider: Schema.String,
138
+ operation: Schema.String,
139
+ message: Schema.String
140
+ }) {};
141
+ const Model = Schema.Union([
142
+ InvalidInput,
143
+ Unauthenticated,
144
+ Forbidden,
145
+ NotFound,
146
+ Conflict,
147
+ Stale,
148
+ Expired,
149
+ Busy,
150
+ ProviderRejected,
151
+ ProviderUnavailable,
152
+ ProviderConflict,
153
+ Unsupported,
154
+ Reconnect,
155
+ StorageFailed,
156
+ CryptoFailed,
157
+ ResolverFailed
158
+ ]);
159
+ //#endregion
160
+ export { Busy, Conflict, CryptoFailed, Expired, Forbidden, InvalidInput, Model, NotFound, ProviderConflict, ProviderRejected, ProviderUnavailable, Reason_exports, Reconnect, ResolverFailed, Stale, StorageFailed, Unauthenticated, Unsupported };
161
+
162
+ //# sourceMappingURL=Reason.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Reason.mjs","names":["Plan.PlanId","Plan.Digest","Plan.Conflict"],"sources":["../src/Reason.ts"],"sourcesContent":["/**\n * Why an operation failed. Every `DomainKit.Error` carries exactly one of these as `reason`; hosts\n * match on `reason._tag` and add a reason here rather than a new error class.\n */\nimport { Schema } from \"effect\";\n\nimport * as Plan from \"./Plan.ts\";\n\n// Plan imports `internal/error.ts`, which imports this module; read Plan lazily so evaluation\n// order inside the cycle does not matter.\nconst PlanId = Schema.suspend(() => Plan.PlanId);\nconst Digest = Schema.suspend(() => Plan.Digest);\nconst Conflicts = Schema.suspend(() => Schema.Array(Plan.Conflict));\n\nexport class InvalidInput extends Schema.TaggedError<InvalidInput>(\n \"@domainkit/Reason/InvalidInput\",\n)(\"InvalidInput\", { message: Schema.String, field: Schema.optionalKey(Schema.String) }) {}\nexport class Unauthenticated extends Schema.TaggedError<Unauthenticated>(\n \"@domainkit/Reason/Unauthenticated\",\n)(\"Unauthenticated\", { message: Schema.String }) {}\nexport class Forbidden extends Schema.TaggedError<Forbidden>(\"@domainkit/Reason/Forbidden\")(\n \"Forbidden\",\n { message: Schema.String },\n) {}\nexport class NotFound extends Schema.TaggedError<NotFound>(\"@domainkit/Reason/NotFound\")(\n \"NotFound\",\n {\n entity: Schema.Literals([\n \"connection\",\n \"attachment\",\n \"plan\",\n \"approval\",\n \"receipt\",\n \"continuation\",\n \"provider\",\n \"zone\",\n \"authorization\",\n \"record\",\n ]),\n id: Schema.String,\n },\n) {\n override get message(): string {\n return `${this.entity} ${this.id} was not found`;\n }\n}\n/** The plan contains conflicts and cannot be applied. */\nexport class Conflict extends Schema.TaggedError<Conflict>(\"@domainkit/Reason/Conflict\")(\n \"Conflict\",\n {\n planId: PlanId,\n operations: Conflicts,\n },\n) {\n override get message(): string {\n return `plan ${this.planId} has ${this.operations.length} conflicting operation(s)`;\n }\n}\n/** Provider state moved since the plan was built; build a new plan. */\nexport class Stale extends Schema.TaggedError<Stale>(\"@domainkit/Reason/Stale\")(\"Stale\", {\n planId: PlanId,\n digest: Digest,\n}) {\n override get message(): string {\n return `plan ${this.planId} is stale; provider state no longer matches digest ${this.digest}`;\n }\n}\nexport class Expired extends Schema.TaggedError<Expired>(\"@domainkit/Reason/Expired\")(\"Expired\", {\n entity: Schema.Literals([\"plan\", \"approval\", \"continuation\", \"credential\"]),\n id: Schema.String,\n}) {\n override get message(): string {\n return `${this.entity} ${this.id} has expired`;\n }\n}\n/** Another operation holds the lock (e.g. concurrent apply or refresh). Retrying is safe. */\nexport class Busy extends Schema.TaggedError<Busy>(\"@domainkit/Reason/Busy\")(\"Busy\", {\n key: Schema.String,\n}) {\n override get message(): string {\n return `${this.key} is busy`;\n }\n}\nexport class ProviderRejected extends Schema.TaggedError<ProviderRejected>(\n \"@domainkit/Reason/ProviderRejected\",\n)(\"ProviderRejected\", {\n provider: Schema.String,\n code: Schema.optionalKey(Schema.String),\n message: Schema.String,\n}) {}\nexport class ProviderUnavailable extends Schema.TaggedError<ProviderUnavailable>(\n \"@domainkit/Reason/ProviderUnavailable\",\n)(\"ProviderUnavailable\", {\n provider: Schema.String,\n retryAfterMs: Schema.optionalKey(Schema.Number),\n message: Schema.String,\n}) {}\n/** The provider credential no longer works; the customer must reconnect. */\nexport class Reconnect extends Schema.TaggedError<Reconnect>(\"@domainkit/Reason/Reconnect\")(\n \"Reconnect\",\n { provider: Schema.String, connectionId: Schema.String },\n) {\n override get message(): string {\n return `${this.provider} connection ${this.connectionId} must be reconnected`;\n }\n}\nexport class StorageFailed extends Schema.TaggedError<StorageFailed>(\n \"@domainkit/Reason/StorageFailed\",\n)(\"StorageFailed\", { operation: Schema.String, message: Schema.String }) {}\nexport class CryptoFailed extends Schema.TaggedError<CryptoFailed>(\n \"@domainkit/Reason/CryptoFailed\",\n)(\"CryptoFailed\", { operation: Schema.Literals([\"digest\", \"seal\", \"open\"]) }) {\n override get message(): string {\n return `crypto ${this.operation} failed`;\n }\n}\nexport class ResolverFailed extends Schema.TaggedError<ResolverFailed>(\n \"@domainkit/Reason/ResolverFailed\",\n)(\"ResolverFailed\", { resolver: Schema.String, message: Schema.String }) {}\n\n/** The provider refused the write because a conflicting record already exists there. */\nexport class ProviderConflict extends Schema.TaggedError<ProviderConflict>(\n \"@domainkit/Reason/ProviderConflict\",\n)(\"ProviderConflict\", {\n provider: Schema.String,\n code: Schema.optionalKey(Schema.String),\n message: Schema.String,\n}) {}\n/** The provider, or this target within it, cannot do the requested operation. */\nexport class Unsupported extends Schema.TaggedError<Unsupported>(\"@domainkit/Reason/Unsupported\")(\n \"Unsupported\",\n { provider: Schema.String, operation: Schema.String, message: Schema.String },\n) {}\n\nexport const Model = Schema.Union([\n InvalidInput,\n Unauthenticated,\n Forbidden,\n NotFound,\n Conflict,\n Stale,\n Expired,\n Busy,\n ProviderRejected,\n ProviderUnavailable,\n ProviderConflict,\n Unsupported,\n Reconnect,\n StorageFailed,\n CryptoFailed,\n ResolverFailed,\n]);\nexport type Model = typeof Model.Type;\n"],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
2
+ import { Digest, Kind, OperationId, PlanId } from "./Plan.mjs";
3
+ import { decode as decode$1 } from "./internal/error.mjs";
4
+ import { ApprovalId } from "./Approval.mjs";
5
+ import { Schema } from "effect";
6
+ //#region src/Receipt.ts
7
+ /**
8
+ * What actually happened when an approval was applied. Partial application is a normal outcome
9
+ * carried here as `status: "partial"`, never an error. Cleanup plans are built only from receipts.
10
+ */
11
+ var Receipt_exports = /* @__PURE__ */ __exportAll({
12
+ Applied: () => Applied,
13
+ Failed: () => Failed,
14
+ Model: () => Model,
15
+ Outcome: () => Outcome,
16
+ ReceiptId: () => ReceiptId,
17
+ Skipped: () => Skipped,
18
+ applied: () => applied,
19
+ decode: () => decode,
20
+ encode: () => encode,
21
+ isComplete: () => isComplete
22
+ });
23
+ const ReceiptId = Schema.String.pipe(Schema.brand("@domainkit/ReceiptId"));
24
+ var Applied = class extends Schema.TaggedClass("@domainkit/Receipt/Applied")("Applied", {
25
+ operationId: OperationId,
26
+ /** The record created (provisioning) or deleted (cleanup). */
27
+ providerRecordId: Schema.String
28
+ }) {};
29
+ /**
30
+ * `noop`: the record already existed. `not-approved`: the approval excluded the operation.
31
+ * `not-attempted`: an earlier operation failed and apply stopped before reaching this one.
32
+ */
33
+ var Skipped = class extends Schema.TaggedClass("@domainkit/Receipt/Skipped")("Skipped", {
34
+ operationId: OperationId,
35
+ reason: Schema.Literals([
36
+ "noop",
37
+ "not-approved",
38
+ "not-attempted"
39
+ ])
40
+ }) {};
41
+ var Failed = class extends Schema.TaggedClass("@domainkit/Receipt/Failed")("Failed", {
42
+ operationId: OperationId,
43
+ message: Schema.String
44
+ }) {};
45
+ const Outcome = Schema.Union([
46
+ Applied,
47
+ Skipped,
48
+ Failed
49
+ ]);
50
+ var Model = class extends Schema.Class("@domainkit/Receipt")({
51
+ id: ReceiptId,
52
+ version: Schema.Literal("domainkit.receipt.v2"),
53
+ kind: Kind,
54
+ planId: PlanId,
55
+ approvalId: ApprovalId,
56
+ digest: Digest,
57
+ provider: Schema.String,
58
+ zone: Schema.String,
59
+ status: Schema.Literals(["complete", "partial"]),
60
+ outcomes: Schema.Array(Outcome),
61
+ appliedAt: Schema.DateTimeUtcFromString
62
+ }) {};
63
+ const decode = (input) => decode$1(Model, input);
64
+ const encode = Schema.encodeSync(Model);
65
+ const isComplete = (receipt) => receipt.status === "complete";
66
+ /** Operations the receipt proves were written, with the provider record ids needed to undo them. */
67
+ const applied = (receipt) => receipt.outcomes.filter((outcome) => outcome._tag === "Applied");
68
+ //#endregion
69
+ export { Applied, Failed, Model, Outcome, ReceiptId, Receipt_exports, Skipped, applied, decode, encode, isComplete };
70
+
71
+ //# sourceMappingURL=Receipt.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Receipt.mjs","names":["Plan.OperationId","Plan.Kind","Plan.PlanId","Approval.ApprovalId","Plan.Digest","Errors.decode"],"sources":["../src/Receipt.ts"],"sourcesContent":["/**\n * What actually happened when an approval was applied. Partial application is a normal outcome\n * carried here as `status: \"partial\"`, never an error. Cleanup plans are built only from receipts.\n */\nimport { Effect, Schema } from \"effect\";\n\nimport * as Approval from \"./Approval.ts\";\nimport * as Errors from \"./internal/error.ts\";\nimport * as Plan from \"./Plan.ts\";\n\nexport const ReceiptId = Schema.String.pipe(Schema.brand(\"@domainkit/ReceiptId\"));\nexport type ReceiptId = typeof ReceiptId.Type;\n\nexport class Applied extends Schema.TaggedClass<Applied>(\"@domainkit/Receipt/Applied\")(\"Applied\", {\n operationId: Plan.OperationId,\n /** The record created (provisioning) or deleted (cleanup). */\n providerRecordId: Schema.String,\n}) {}\n/**\n * `noop`: the record already existed. `not-approved`: the approval excluded the operation.\n * `not-attempted`: an earlier operation failed and apply stopped before reaching this one.\n */\nexport class Skipped extends Schema.TaggedClass<Skipped>(\"@domainkit/Receipt/Skipped\")(\"Skipped\", {\n operationId: Plan.OperationId,\n reason: Schema.Literals([\"noop\", \"not-approved\", \"not-attempted\"]),\n}) {}\nexport class Failed extends Schema.TaggedClass<Failed>(\"@domainkit/Receipt/Failed\")(\"Failed\", {\n operationId: Plan.OperationId,\n message: Schema.String,\n}) {}\nexport const Outcome = Schema.Union([Applied, Skipped, Failed]);\nexport type Outcome = typeof Outcome.Type;\n\nexport class Model extends Schema.Class<Model>(\"@domainkit/Receipt\")({\n id: ReceiptId,\n version: Schema.Literal(\"domainkit.receipt.v2\"),\n kind: Plan.Kind,\n planId: Plan.PlanId,\n approvalId: Approval.ApprovalId,\n digest: Plan.Digest,\n provider: Schema.String,\n zone: Schema.String,\n status: Schema.Literals([\"complete\", \"partial\"]),\n outcomes: Schema.Array(Outcome),\n appliedAt: Schema.DateTimeUtcFromString,\n}) {}\nexport type Encoded = typeof Model.Encoded;\n\nexport const decode = (input: unknown): Effect.Effect<Model, Errors.DomainKitError> =>\n Errors.decode(Model, input);\nexport const encode: (receipt: Model) => Encoded = Schema.encodeSync(Model);\nexport const isComplete = (receipt: Model): boolean => receipt.status === \"complete\";\n\n/** Operations the receipt proves were written, with the provider record ids needed to undo them. */\nexport const applied = (receipt: Model): ReadonlyArray<Applied> =>\n receipt.outcomes.filter((outcome): outcome is Applied => outcome._tag === \"Applied\");\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAUA,MAAa,YAAY,OAAO,OAAO,KAAK,OAAO,MAAM,sBAAsB,CAAC;AAGhF,IAAa,UAAb,cAA6B,OAAO,YAAqB,4BAA4B,CAAC,CAAC,WAAW;CAChG,aAAaA;;CAEb,kBAAkB,OAAO;AAC3B,CAAC,CAAC,CAAC,CAAC;;;;;AAKJ,IAAa,UAAb,cAA6B,OAAO,YAAqB,4BAA4B,CAAC,CAAC,WAAW;CAChG,aAAaA;CACb,QAAQ,OAAO,SAAS;EAAC;EAAQ;EAAgB;CAAe,CAAC;AACnE,CAAC,CAAC,CAAC,CAAC;AACJ,IAAa,SAAb,cAA4B,OAAO,YAAoB,2BAA2B,CAAC,CAAC,UAAU;CAC5F,aAAaA;CACb,SAAS,OAAO;AAClB,CAAC,CAAC,CAAC,CAAC;AACJ,MAAa,UAAU,OAAO,MAAM;CAAC;CAAS;CAAS;AAAM,CAAC;AAG9D,IAAa,QAAb,cAA2B,OAAO,MAAa,oBAAoB,CAAC,CAAC;CACnE,IAAI;CACJ,SAAS,OAAO,QAAQ,sBAAsB;CAC9C,MAAMC;CACN,QAAQC;CACR,YAAYC;CACZ,QAAQC;CACR,UAAU,OAAO;CACjB,MAAM,OAAO;CACb,QAAQ,OAAO,SAAS,CAAC,YAAY,SAAS,CAAC;CAC/C,UAAU,OAAO,MAAM,OAAO;CAC9B,WAAW,OAAO;AACpB,CAAC,CAAC,CAAC,CAAC;AAGJ,MAAa,UAAU,UACrBC,SAAc,OAAO,KAAK;AAC5B,MAAa,SAAsC,OAAO,WAAW,KAAK;AAC1E,MAAa,cAAc,YAA4B,QAAQ,WAAW;;AAG1E,MAAa,WAAW,YACtB,QAAQ,SAAS,QAAQ,YAAgC,QAAQ,SAAS,SAAS"}