domainkit 0.7.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.
- package/README.md +214 -39
- package/dist/Approval.mjs +34 -0
- package/dist/Approval.mjs.map +1 -0
- package/dist/Cleanup.mjs +97 -0
- package/dist/Cleanup.mjs.map +1 -0
- package/dist/Cloudflare.mjs +225 -0
- package/dist/Cloudflare.mjs.map +1 -0
- package/dist/Connect.mjs +463 -0
- package/dist/Connect.mjs.map +1 -0
- package/dist/Custody.mjs +64 -0
- package/dist/Custody.mjs.map +1 -0
- package/dist/DnsRecord.mjs +213 -0
- package/dist/DnsRecord.mjs.map +1 -0
- package/dist/DomainKit.mjs +34 -0
- package/dist/DomainKit.mjs.map +1 -0
- package/dist/DomainName.mjs +100 -0
- package/dist/DomainName.mjs.map +1 -0
- package/dist/Plan.mjs +102 -0
- package/dist/Plan.mjs.map +1 -0
- package/dist/Principal.mjs +23 -0
- package/dist/Principal.mjs.map +1 -0
- package/dist/Provider.mjs +130 -0
- package/dist/Provider.mjs.map +1 -0
- package/dist/Providers.mjs +40 -0
- package/dist/Providers.mjs.map +1 -0
- package/dist/Provision.mjs +93 -0
- package/dist/Provision.mjs.map +1 -0
- package/dist/Reason.mjs +162 -0
- package/dist/Reason.mjs.map +1 -0
- package/dist/Receipt.mjs +71 -0
- package/dist/Receipt.mjs.map +1 -0
- package/dist/Resolver.mjs +75 -0
- package/dist/Resolver.mjs.map +1 -0
- package/dist/Server.mjs +516 -0
- package/dist/Server.mjs.map +1 -0
- package/dist/Storage.mjs +236 -0
- package/dist/Storage.mjs.map +1 -0
- package/dist/Testing.mjs +226 -0
- package/dist/Testing.mjs.map +1 -0
- package/dist/Transport.mjs +187 -0
- package/dist/Transport.mjs.map +1 -0
- package/dist/Vercel.mjs +116 -0
- package/dist/Vercel.mjs.map +1 -0
- package/dist/Verify.mjs +292 -0
- package/dist/Verify.mjs.map +1 -0
- package/dist/{rolldown-runtime-D7D4PA-g.mjs → _virtual/_rolldown/runtime.mjs} +1 -1
- package/dist/entry/client.mjs +2 -0
- package/dist/entry/server.mjs +2 -0
- package/dist/entry/testing.mjs +3 -0
- package/dist/index.mjs +33 -356
- package/dist/index.mjs.map +1 -1
- package/dist/internal/aes.mjs +61 -0
- package/dist/internal/aes.mjs.map +1 -0
- package/dist/internal/attempts.mjs +284 -0
- package/dist/internal/attempts.mjs.map +1 -0
- package/dist/internal/cloudflare/client.mjs +100 -0
- package/dist/internal/cloudflare/client.mjs.map +1 -0
- package/dist/internal/cloudflare/protocol.mjs +59 -0
- package/dist/internal/cloudflare/protocol.mjs.map +1 -0
- package/dist/internal/cloudflare/records.mjs +156 -0
- package/dist/internal/cloudflare/records.mjs.map +1 -0
- package/dist/internal/config.mjs +13 -0
- package/dist/internal/config.mjs.map +1 -0
- package/dist/internal/conformance/provider.mjs +207 -0
- package/dist/internal/conformance/provider.mjs.map +1 -0
- package/dist/internal/conformance/storage.mjs +428 -0
- package/dist/internal/conformance/storage.mjs.map +1 -0
- package/dist/internal/digest.mjs +25 -0
- package/dist/internal/digest.mjs.map +1 -0
- package/dist/internal/doh.mjs +152 -0
- package/dist/internal/doh.mjs.map +1 -0
- package/dist/internal/error.mjs +69 -0
- package/dist/internal/error.mjs.map +1 -0
- package/dist/internal/http.mjs +89 -0
- package/dist/internal/http.mjs.map +1 -0
- package/dist/internal/ids.mjs +8 -0
- package/dist/internal/ids.mjs.map +1 -0
- package/dist/internal/oauth.mjs +113 -0
- package/dist/internal/oauth.mjs.map +1 -0
- package/dist/internal/planner.mjs +88 -0
- package/dist/internal/planner.mjs.map +1 -0
- package/dist/internal/provider-async.mjs +83 -0
- package/dist/internal/provider-async.mjs.map +1 -0
- package/dist/internal/storage-memory.mjs +302 -0
- package/dist/internal/storage-memory.mjs.map +1 -0
- package/dist/internal/vercel/client.mjs +122 -0
- package/dist/internal/vercel/client.mjs.map +1 -0
- package/dist/internal/vercel/protocol.mjs +80 -0
- package/dist/internal/vercel/protocol.mjs.map +1 -0
- package/dist/internal/vercel/records.mjs +151 -0
- package/dist/internal/vercel/records.mjs.map +1 -0
- package/dist/package.mjs +6 -0
- package/dist/package.mjs.map +1 -0
- package/dist/types/Approval.d.ts +26 -0
- package/dist/types/Cleanup.d.ts +41 -0
- package/dist/types/Cloudflare.d.ts +36 -0
- package/dist/types/Connect.d.ts +506 -0
- package/dist/types/Custody.d.ts +38 -0
- package/dist/types/DnsRecord.d.ts +158 -0
- package/dist/types/DomainKit.d.ts +31 -0
- package/dist/types/DomainName.d.ts +27 -0
- package/dist/types/Plan.d.ts +76 -0
- package/dist/types/Principal.d.ts +18 -0
- package/dist/types/Provider.d.ts +248 -0
- package/dist/types/Providers.d.ts +16 -0
- package/dist/types/Provision.d.ts +75 -0
- package/dist/types/Reason.d.ts +118 -0
- package/dist/types/Receipt.d.ts +55 -0
- package/dist/types/Resolver.d.ts +135 -0
- package/dist/types/Server.d.ts +6523 -0
- package/dist/types/Storage.d.ts +314 -0
- package/dist/types/Testing.d.ts +84 -0
- package/dist/types/Transport.d.ts +124 -0
- package/dist/types/Vercel.d.ts +25 -0
- package/dist/types/Verify.d.ts +109 -0
- package/dist/types/entry/client.d.ts +2 -0
- package/dist/types/entry/server.d.ts +2 -0
- package/dist/types/entry/testing.d.ts +3 -0
- package/dist/types/index.d.ts +21 -0
- package/dist/types/internal/aes.d.ts +8 -0
- package/dist/types/internal/attempts.d.ts +40 -0
- package/dist/types/internal/cloudflare/client.d.ts +23 -0
- package/dist/types/internal/cloudflare/protocol.d.ts +71 -0
- package/dist/types/internal/cloudflare/records.d.ts +8 -0
- package/dist/types/internal/config.d.ts +4 -0
- package/dist/types/internal/conformance/provider.d.ts +12 -0
- package/dist/types/internal/conformance/storage.d.ts +14 -0
- package/dist/types/internal/digest.d.ts +10 -0
- package/dist/types/internal/doh.d.ts +19 -0
- package/dist/types/internal/error.d.ts +24 -0
- package/dist/types/internal/http.d.ts +27 -0
- package/dist/types/internal/ids.d.ts +3 -0
- package/dist/types/internal/oauth.d.ts +58 -0
- package/dist/types/internal/planner.d.ts +22 -0
- package/dist/types/internal/provider-async.d.ts +78 -0
- package/dist/types/internal/storage-memory.d.ts +2 -0
- package/dist/types/internal/vercel/client.d.ts +29 -0
- package/dist/types/internal/vercel/protocol.d.ts +124 -0
- package/dist/types/internal/vercel/records.d.ts +7 -0
- package/package.json +88 -12
- package/dist/canonical-json-CQYKsZw7.mjs +0 -310
- package/dist/canonical-json-CQYKsZw7.mjs.map +0 -1
- package/dist/connect-10vHuShV.d.mts +0 -1401
- package/dist/connect-oI8hcMxc.mjs +0 -185
- package/dist/connect-oI8hcMxc.mjs.map +0 -1
- package/dist/connection-C0xsU41C.d.mts +0 -186
- package/dist/connection-api-DW7xUJdj.d.mts +0 -26
- package/dist/index.d.mts +0 -882
- package/dist/managed-dns-connections-C3A_GBaz.mjs +0 -99
- package/dist/managed-dns-connections-C3A_GBaz.mjs.map +0 -1
- package/dist/promise.d.mts +0 -378
- package/dist/promise.mjs +0 -598
- package/dist/promise.mjs.map +0 -1
- package/dist/resolver-DPJs8h0a.mjs +0 -859
- package/dist/resolver-DPJs8h0a.mjs.map +0 -1
- package/dist/resolver-DRrLm_hB.d.mts +0 -3734
- package/dist/server.d.mts +0 -128
- package/dist/server.mjs +0 -319
- package/dist/server.mjs.map +0 -1
- package/dist/testing.d.mts +0 -62
- package/dist/testing.mjs +0 -507
- package/dist/testing.mjs.map +0 -1
- package/dist/verify-C3V_GtzL.d.mts +0 -1268
- package/dist/verify-DDnK8U7C.mjs +0 -2396
- package/dist/verify-DDnK8U7C.mjs.map +0 -1
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { isWithin } from "../DomainName.mjs";
|
|
2
|
+
import { equals } from "../DnsRecord.mjs";
|
|
3
|
+
import { Conflict, Delete, Model, PlanId, conflicts, writes } from "../Plan.mjs";
|
|
4
|
+
import { Conflict as Conflict$1, Expired, InvalidInput, NotFound, Stale } from "../Reason.mjs";
|
|
5
|
+
import { fail } from "./error.mjs";
|
|
6
|
+
import { ApprovalId, Model as Model$1 } from "../Approval.mjs";
|
|
7
|
+
import { fresh } from "./ids.mjs";
|
|
8
|
+
import { Service } from "../Principal.mjs";
|
|
9
|
+
import { Applied, Failed, Model as Model$2, ReceiptId, Skipped } from "../Receipt.mjs";
|
|
10
|
+
import { Attempt } from "../Storage.mjs";
|
|
11
|
+
import { digest, reconcile, requirements } from "./planner.mjs";
|
|
12
|
+
import { DateTime, Duration, Effect } from "effect";
|
|
13
|
+
//#region src/internal/attempts.ts
|
|
14
|
+
const stale = (plan) => fail(new Stale({
|
|
15
|
+
planId: plan.id,
|
|
16
|
+
digest: plan.digest
|
|
17
|
+
}));
|
|
18
|
+
const expired = (entity, id) => fail(new Expired({
|
|
19
|
+
entity,
|
|
20
|
+
id
|
|
21
|
+
}));
|
|
22
|
+
const past = (at, now) => DateTime.toEpochMillis(at) <= DateTime.toEpochMillis(now);
|
|
23
|
+
/** Every requirement must sit at or below the attached domain; other tenants share the zone. */
|
|
24
|
+
const assertWithin = (attachment, records) => {
|
|
25
|
+
const outside = records.filter((record) => !isWithin(record.name, attachment.domain));
|
|
26
|
+
return outside.length === 0 ? Effect.void : fail(new InvalidInput({
|
|
27
|
+
message: `${outside.map(({ name }) => name).join(", ")} is outside the attached domain ${attachment.domain}`,
|
|
28
|
+
field: "requirements"
|
|
29
|
+
}));
|
|
30
|
+
};
|
|
31
|
+
/** Shared plan -> approve -> apply machinery for provisioning and cleanup attempts. */
|
|
32
|
+
const make = (storage, connect, kind) => {
|
|
33
|
+
const providerOf = (attachment) => Effect.gen(function* () {
|
|
34
|
+
const connection = yield* storage.connections.get(attachment.connectionId);
|
|
35
|
+
return (yield* storage.authorizations.get(connection.authorizationId)).provider;
|
|
36
|
+
});
|
|
37
|
+
/** Persist a freshly built plan as a `planned` attempt. */
|
|
38
|
+
const record = (input) => Effect.gen(function* () {
|
|
39
|
+
const principal = yield* Service;
|
|
40
|
+
const provider = yield* providerOf(input.attachment);
|
|
41
|
+
const now = yield* DateTime.now;
|
|
42
|
+
const unsigned = {
|
|
43
|
+
version: "domainkit.plan.v2",
|
|
44
|
+
kind,
|
|
45
|
+
domain: input.attachment.domain,
|
|
46
|
+
zone: input.target.zone,
|
|
47
|
+
provider,
|
|
48
|
+
attachmentId: input.attachment.id,
|
|
49
|
+
operations: input.operations
|
|
50
|
+
};
|
|
51
|
+
const plan = new Model({
|
|
52
|
+
...unsigned,
|
|
53
|
+
id: PlanId.make(yield* fresh("plan")),
|
|
54
|
+
digest: yield* digest(unsigned),
|
|
55
|
+
createdAt: now,
|
|
56
|
+
expiresAt: DateTime.addDuration(now, Duration.millis(input.ttlMs))
|
|
57
|
+
});
|
|
58
|
+
yield* storage.attempts.create(new Attempt({
|
|
59
|
+
id: plan.id,
|
|
60
|
+
ownerId: principal.ownerId,
|
|
61
|
+
attachmentId: input.attachment.id,
|
|
62
|
+
kind,
|
|
63
|
+
status: "planned",
|
|
64
|
+
plan,
|
|
65
|
+
approval: null,
|
|
66
|
+
receipt: null,
|
|
67
|
+
rejection: null,
|
|
68
|
+
sourceReceiptId: input.sourceReceiptId,
|
|
69
|
+
leaseExpiresAt: null,
|
|
70
|
+
failure: null,
|
|
71
|
+
updatedAt: now
|
|
72
|
+
}));
|
|
73
|
+
return plan;
|
|
74
|
+
});
|
|
75
|
+
const attemptOf = (plan) => Effect.gen(function* () {
|
|
76
|
+
const attempt = yield* storage.attempts.get(typeof plan === "string" ? plan : plan.id);
|
|
77
|
+
if (attempt.kind !== kind) return yield* fail(new NotFound({
|
|
78
|
+
entity: "plan",
|
|
79
|
+
id: attempt.id
|
|
80
|
+
}));
|
|
81
|
+
if (typeof plan !== "string" && plan.digest !== attempt.plan.digest) return yield* stale(plan);
|
|
82
|
+
return attempt;
|
|
83
|
+
});
|
|
84
|
+
const approve = (plan, options = {}) => Effect.gen(function* () {
|
|
85
|
+
const attempt = yield* attemptOf(plan);
|
|
86
|
+
if (attempt.approval !== null) return attempt.approval;
|
|
87
|
+
const principal = yield* Service;
|
|
88
|
+
const now = yield* DateTime.now;
|
|
89
|
+
if (past(attempt.plan.expiresAt, now)) return yield* expired("plan", attempt.plan.id);
|
|
90
|
+
const writes$1 = writes(attempt.plan);
|
|
91
|
+
const writeIds = new Set(writes$1.map(({ id }) => id));
|
|
92
|
+
const selected = [...new Set(options.operationIds ?? writes$1.map(({ id }) => id))].sort();
|
|
93
|
+
const unknown = selected.filter((id) => !writeIds.has(id));
|
|
94
|
+
if (unknown.length > 0) return yield* fail(new InvalidInput({
|
|
95
|
+
message: `Operations ${unknown.join(", ")} are not writable in plan ${attempt.plan.id}`,
|
|
96
|
+
field: "operationIds"
|
|
97
|
+
}));
|
|
98
|
+
const conflicts$1 = conflicts(attempt.plan);
|
|
99
|
+
if (options.allowPartial !== true) {
|
|
100
|
+
if (conflicts$1.length > 0) return yield* fail(new Conflict$1({
|
|
101
|
+
planId: attempt.plan.id,
|
|
102
|
+
operations: conflicts$1
|
|
103
|
+
}));
|
|
104
|
+
if (selected.length !== writes$1.length) return yield* fail(new InvalidInput({
|
|
105
|
+
message: "Approving a subset of the plan requires allowPartial",
|
|
106
|
+
field: "operationIds"
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
const approval = new Model$1({
|
|
110
|
+
id: ApprovalId.make(yield* fresh("apr")),
|
|
111
|
+
version: "domainkit.approval.v2",
|
|
112
|
+
kind,
|
|
113
|
+
planId: attempt.plan.id,
|
|
114
|
+
digest: attempt.plan.digest,
|
|
115
|
+
operationIds: selected,
|
|
116
|
+
approvedBy: principal.actorId,
|
|
117
|
+
approvedAt: now,
|
|
118
|
+
expiresAt: attempt.plan.expiresAt
|
|
119
|
+
});
|
|
120
|
+
return (yield* storage.attempts.approve(attempt.id, approval)).approval ?? approval;
|
|
121
|
+
});
|
|
122
|
+
/** Decline a planned attempt on behalf of the principal's actor. */
|
|
123
|
+
const reject = (plan, options = {}) => Effect.gen(function* () {
|
|
124
|
+
const attempt = yield* attemptOf(plan);
|
|
125
|
+
const principal = yield* Service;
|
|
126
|
+
return yield* storage.attempts.reject(attempt.id, {
|
|
127
|
+
digest: attempt.plan.digest,
|
|
128
|
+
actorId: principal.actorId,
|
|
129
|
+
reason: options.reason ?? null
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
const apply = (approval, policy) => Effect.gen(function* () {
|
|
133
|
+
const attempt = typeof approval === "string" ? yield* storage.attempts.byApproval(approval) : yield* attemptOf(approval.planId);
|
|
134
|
+
if (attempt.kind !== kind) return yield* fail(new NotFound({
|
|
135
|
+
entity: "approval",
|
|
136
|
+
id: typeof approval === "string" ? approval : approval.id
|
|
137
|
+
}));
|
|
138
|
+
const stored = attempt.approval;
|
|
139
|
+
if (stored === null || typeof approval !== "string" && stored.id !== approval.id) return yield* fail(new NotFound({
|
|
140
|
+
entity: "approval",
|
|
141
|
+
id: typeof approval === "string" ? approval : approval.id
|
|
142
|
+
}));
|
|
143
|
+
if (attempt.receipt !== null) return attempt.receipt;
|
|
144
|
+
const now = yield* DateTime.now;
|
|
145
|
+
if (past(stored.expiresAt, now)) return yield* expired("approval", stored.id);
|
|
146
|
+
const claimed = yield* storage.attempts.claim(attempt.id, DateTime.addDuration(now, Duration.millis(policy.applyLeaseMs)));
|
|
147
|
+
return yield* run(claimed, stored).pipe(Effect.tapError((error) => storage.attempts.fail(claimed.id, error.message).pipe(Effect.ignore)));
|
|
148
|
+
});
|
|
149
|
+
/** Writes under a claimed lease; a failure before any write releases the attempt as `failed`. */
|
|
150
|
+
const run = (attempt, approval) => Effect.gen(function* () {
|
|
151
|
+
const plan = attempt.plan;
|
|
152
|
+
const attachment = yield* storage.attachments.get(attempt.attachmentId);
|
|
153
|
+
const { session, target } = yield* connect.session(attachment.id);
|
|
154
|
+
const dns = session.dns(target);
|
|
155
|
+
yield* revalidate(plan, dns, target.zone);
|
|
156
|
+
const approved = new Set(approval.operationIds);
|
|
157
|
+
const outcomes = [];
|
|
158
|
+
const applied = [];
|
|
159
|
+
let stopped = false;
|
|
160
|
+
const finish = (status) => Effect.gen(function* () {
|
|
161
|
+
const receipt = new Model$2({
|
|
162
|
+
id: ReceiptId.make(yield* fresh("rcpt")),
|
|
163
|
+
version: "domainkit.receipt.v2",
|
|
164
|
+
kind,
|
|
165
|
+
planId: plan.id,
|
|
166
|
+
approvalId: approval.id,
|
|
167
|
+
digest: plan.digest,
|
|
168
|
+
provider: plan.provider,
|
|
169
|
+
zone: plan.zone,
|
|
170
|
+
status,
|
|
171
|
+
outcomes: [...outcomes],
|
|
172
|
+
appliedAt: yield* DateTime.now
|
|
173
|
+
});
|
|
174
|
+
yield* storage.attempts.complete(attempt.id, receipt);
|
|
175
|
+
return receipt;
|
|
176
|
+
});
|
|
177
|
+
return yield* Effect.gen(function* () {
|
|
178
|
+
for (const operation of plan.operations) {
|
|
179
|
+
if (operation._tag === "Noop") {
|
|
180
|
+
outcomes.push(new Skipped({
|
|
181
|
+
operationId: operation.id,
|
|
182
|
+
reason: "noop"
|
|
183
|
+
}));
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (operation._tag === "Conflict" || !approved.has(operation.id)) {
|
|
187
|
+
outcomes.push(new Skipped({
|
|
188
|
+
operationId: operation.id,
|
|
189
|
+
reason: "not-approved"
|
|
190
|
+
}));
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (stopped) {
|
|
194
|
+
outcomes.push(new Skipped({
|
|
195
|
+
operationId: operation.id,
|
|
196
|
+
reason: "not-attempted"
|
|
197
|
+
}));
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const result = yield* write(plan, operation, dns, target.zone).pipe(Effect.result);
|
|
201
|
+
if (result._tag === "Success") {
|
|
202
|
+
const outcome = new Applied({
|
|
203
|
+
operationId: operation.id,
|
|
204
|
+
providerRecordId: result.success
|
|
205
|
+
});
|
|
206
|
+
outcomes.push(outcome);
|
|
207
|
+
applied.push(outcome);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (applied.length === 0) return yield* Effect.fail(result.failure);
|
|
211
|
+
stopped = true;
|
|
212
|
+
outcomes.push(new Failed({
|
|
213
|
+
operationId: operation.id,
|
|
214
|
+
message: result.failure.message
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
return yield* finish(stopped ? "partial" : "complete");
|
|
218
|
+
}).pipe(Effect.onInterrupt(() => applied.length === 0 ? Effect.void : Effect.gen(function* () {
|
|
219
|
+
for (const operation of plan.operations) {
|
|
220
|
+
if (outcomes.some((outcome) => outcome.operationId === operation.id)) continue;
|
|
221
|
+
outcomes.push(new Skipped({
|
|
222
|
+
operationId: operation.id,
|
|
223
|
+
reason: "not-attempted"
|
|
224
|
+
}));
|
|
225
|
+
}
|
|
226
|
+
yield* finish("partial");
|
|
227
|
+
}).pipe(Effect.ignore)));
|
|
228
|
+
});
|
|
229
|
+
/** Re-plan the zone and fail `Stale` when the digest moved since the plan was built. */
|
|
230
|
+
const revalidate = (plan, dns, zone) => Effect.gen(function* () {
|
|
231
|
+
const current = yield* currentOperations(plan, dns, zone);
|
|
232
|
+
if ((yield* digest({
|
|
233
|
+
version: plan.version,
|
|
234
|
+
kind: plan.kind,
|
|
235
|
+
domain: plan.domain,
|
|
236
|
+
zone: plan.zone,
|
|
237
|
+
provider: plan.provider,
|
|
238
|
+
attachmentId: plan.attachmentId,
|
|
239
|
+
operations: current
|
|
240
|
+
})) !== plan.digest) return yield* stale(plan);
|
|
241
|
+
});
|
|
242
|
+
const currentOperations = (plan, dns, zone) => kind === "provisioning" ? Effect.flatMap(dns.list(zone), (observed) => reconcile(requirements(plan), observed.map(({ record: observedRecord }) => observedRecord))) : Effect.forEach(plan.operations, (operation) => operation._tag === "Delete" ? cleanupOperation(dns, zone, operation.id, operation.record, operation.providerRecordId) : Effect.succeed(operation));
|
|
243
|
+
const write = (plan, operation, dns, zone) => operation._tag === "Create" ? Effect.gen(function* () {
|
|
244
|
+
const observed = yield* dns.list(zone);
|
|
245
|
+
const [check] = yield* reconcile([operation.record], observed.map(({ record: observedRecord }) => observedRecord));
|
|
246
|
+
if (check?._tag !== "Create") return yield* stale(plan);
|
|
247
|
+
return (yield* dns.create(zone, operation.record)).providerRecordId;
|
|
248
|
+
}) : Effect.gen(function* () {
|
|
249
|
+
if ((yield* cleanupOperation(dns, zone, operation.id, operation.record, operation.providerRecordId))._tag !== "Delete") return yield* stale(plan);
|
|
250
|
+
yield* dns.delete(zone, operation.providerRecordId);
|
|
251
|
+
return operation.providerRecordId;
|
|
252
|
+
});
|
|
253
|
+
return {
|
|
254
|
+
record,
|
|
255
|
+
approve,
|
|
256
|
+
reject,
|
|
257
|
+
apply,
|
|
258
|
+
attemptOf
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
/** Read one receipt-proven record back; anything but an exact match is a conflict. */
|
|
262
|
+
const cleanupOperation = (dns, zone, id, record, providerRecordId) => Effect.map(dns.get(zone, providerRecordId), (observed) => {
|
|
263
|
+
if (observed === null) return new Conflict({
|
|
264
|
+
id,
|
|
265
|
+
record,
|
|
266
|
+
existing: [],
|
|
267
|
+
reason: "missing"
|
|
268
|
+
});
|
|
269
|
+
if (!equals(observed, record)) return new Conflict({
|
|
270
|
+
id,
|
|
271
|
+
record,
|
|
272
|
+
existing: [observed],
|
|
273
|
+
reason: "value-mismatch"
|
|
274
|
+
});
|
|
275
|
+
return new Delete({
|
|
276
|
+
id,
|
|
277
|
+
record,
|
|
278
|
+
providerRecordId
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
//#endregion
|
|
282
|
+
export { assertWithin, cleanupOperation, make };
|
|
283
|
+
|
|
284
|
+
//# sourceMappingURL=attempts.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attempts.mjs","names":["Errors.fail","Reason.Stale","Reason.Expired","DomainName.isWithin","Reason.InvalidInput","Principal.Service","Plan.Model","Planner.digest","Storage.Attempt","Reason.NotFound","writes","Plan.writes","conflicts","Plan.conflicts","Reason.Conflict","Approval.Model","Receipt.Model","Receipt.Skipped","Receipt.Applied","Receipt.Failed","Planner.reconcile","Planner.requirements","Plan.Conflict","DnsRecord.equals","Plan.Delete"],"sources":["../../src/internal/attempts.ts"],"sourcesContent":["import { DateTime, Duration, Effect } from \"effect\";\n\nimport * as Approval from \"../Approval.ts\";\nimport * as Connect from \"../Connect.ts\";\nimport * as DnsRecord from \"../DnsRecord.ts\";\nimport * as Errors from \"./error.ts\";\nimport * as Reason from \"../Reason.ts\";\nimport * as DomainName from \"../DomainName.ts\";\nimport * as Plan from \"../Plan.ts\";\nimport * as Principal from \"../Principal.ts\";\nimport type * as Provider from \"../Provider.ts\";\nimport * as Receipt from \"../Receipt.ts\";\nimport * as Storage from \"../Storage.ts\";\nimport { fresh } from \"./ids.ts\";\nimport * as Planner from \"./planner.ts\";\n\ntype Fx<A> = Effect.Effect<A, Errors.DomainKitError, Principal.Service>;\n\nexport interface ApproveOptions {\n readonly operationIds?: ReadonlyArray<Plan.OperationId>;\n readonly allowPartial?: boolean;\n}\n\nexport interface PolicyShape {\n readonly planTtlMs: number;\n readonly applyLeaseMs: number;\n}\n\nconst stale = (plan: Plan.Model) =>\n Errors.fail(new Reason.Stale({ planId: plan.id, digest: plan.digest }));\n\nconst expired = (entity: \"plan\" | \"approval\", id: string) =>\n Errors.fail(new Reason.Expired({ entity, id }));\n\nconst past = (at: DateTime.Utc, now: DateTime.Utc) =>\n DateTime.toEpochMillis(at) <= DateTime.toEpochMillis(now);\n\n/** Every requirement must sit at or below the attached domain; other tenants share the zone. */\nexport const assertWithin = (\n attachment: Storage.Attachment,\n records: ReadonlyArray<DnsRecord.Model>,\n): Effect.Effect<void, Errors.DomainKitError> => {\n const outside = records.filter((record) => !DomainName.isWithin(record.name, attachment.domain));\n return outside.length === 0\n ? Effect.void\n : Errors.fail(\n new Reason.InvalidInput({\n message: `${outside.map(({ name }) => name).join(\", \")} is outside the attached domain ${attachment.domain}`,\n field: \"requirements\",\n }),\n );\n};\n\n/** Shared plan -> approve -> apply machinery for provisioning and cleanup attempts. */\nexport const make = (storage: Storage.Interface, connect: Connect.Interface, kind: Plan.Kind) => {\n const providerOf = (attachment: Storage.Attachment) =>\n Effect.gen(function* () {\n const connection = yield* storage.connections.get(attachment.connectionId);\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n return authorization.provider;\n });\n\n /** Persist a freshly built plan as a `planned` attempt. */\n const record = (input: {\n readonly attachment: Storage.Attachment;\n readonly target: Provider.Target;\n readonly operations: ReadonlyArray<Plan.Operation>;\n readonly ttlMs: number;\n readonly sourceReceiptId: Receipt.ReceiptId | null;\n }): Fx<Plan.Model> =>\n Effect.gen(function* () {\n const principal = yield* Principal.Service;\n const provider = yield* providerOf(input.attachment);\n const now = yield* DateTime.now;\n const unsigned: Planner.Unsigned = {\n version: \"domainkit.plan.v2\",\n kind,\n domain: input.attachment.domain,\n zone: input.target.zone,\n provider,\n attachmentId: input.attachment.id,\n operations: input.operations,\n };\n const plan = new Plan.Model({\n ...unsigned,\n id: Plan.PlanId.make(yield* fresh(\"plan\")),\n digest: yield* Planner.digest(unsigned),\n createdAt: now,\n expiresAt: DateTime.addDuration(now, Duration.millis(input.ttlMs)),\n });\n yield* storage.attempts.create(\n new Storage.Attempt({\n id: plan.id,\n ownerId: principal.ownerId,\n attachmentId: input.attachment.id,\n kind,\n status: \"planned\",\n plan,\n approval: null,\n receipt: null,\n rejection: null,\n sourceReceiptId: input.sourceReceiptId,\n leaseExpiresAt: null,\n failure: null,\n updatedAt: now,\n }),\n );\n return plan;\n });\n\n const attemptOf = (plan: Plan.Model | Plan.PlanId): Fx<Storage.Attempt> =>\n Effect.gen(function* () {\n const attempt = yield* storage.attempts.get(typeof plan === \"string\" ? plan : plan.id);\n if (attempt.kind !== kind) {\n return yield* Errors.fail(new Reason.NotFound({ entity: \"plan\", id: attempt.id }));\n }\n if (typeof plan !== \"string\" && plan.digest !== attempt.plan.digest)\n return yield* stale(plan);\n return attempt;\n });\n\n const approve = (\n plan: Plan.Model | Plan.PlanId,\n options: ApproveOptions = {},\n ): Fx<Approval.Model> =>\n Effect.gen(function* () {\n const attempt = yield* attemptOf(plan);\n if (attempt.approval !== null) return attempt.approval;\n const principal = yield* Principal.Service;\n const now = yield* DateTime.now;\n if (past(attempt.plan.expiresAt, now)) return yield* expired(\"plan\", attempt.plan.id);\n const writes = Plan.writes(attempt.plan);\n const writeIds = new Set(writes.map(({ id }) => id));\n const selected = [...new Set(options.operationIds ?? writes.map(({ id }) => id))].sort();\n const unknown = selected.filter((id) => !writeIds.has(id));\n if (unknown.length > 0) {\n return yield* Errors.fail(\n new Reason.InvalidInput({\n message: `Operations ${unknown.join(\", \")} are not writable in plan ${attempt.plan.id}`,\n field: \"operationIds\",\n }),\n );\n }\n const conflicts = Plan.conflicts(attempt.plan);\n if (options.allowPartial !== true) {\n if (conflicts.length > 0) {\n return yield* Errors.fail(\n new Reason.Conflict({ planId: attempt.plan.id, operations: conflicts }),\n );\n }\n if (selected.length !== writes.length) {\n return yield* Errors.fail(\n new Reason.InvalidInput({\n message: \"Approving a subset of the plan requires allowPartial\",\n field: \"operationIds\",\n }),\n );\n }\n }\n const approval = new Approval.Model({\n id: Approval.ApprovalId.make(yield* fresh(\"apr\")),\n version: \"domainkit.approval.v2\",\n kind,\n planId: attempt.plan.id,\n digest: attempt.plan.digest,\n operationIds: selected,\n approvedBy: principal.actorId,\n approvedAt: now,\n expiresAt: attempt.plan.expiresAt,\n });\n const stored = yield* storage.attempts.approve(attempt.id, approval);\n return stored.approval ?? approval;\n });\n\n /** Decline a planned attempt on behalf of the principal's actor. */\n const reject = (\n plan: Plan.Model | Plan.PlanId,\n options: { readonly reason?: string } = {},\n ): Fx<Storage.Attempt> =>\n Effect.gen(function* () {\n const attempt = yield* attemptOf(plan);\n const principal = yield* Principal.Service;\n return yield* storage.attempts.reject(attempt.id, {\n digest: attempt.plan.digest,\n actorId: principal.actorId,\n reason: options.reason ?? null,\n });\n });\n\n const apply = (\n approval: Approval.Model | Approval.ApprovalId,\n policy: PolicyShape,\n ): Fx<Receipt.Model> =>\n Effect.gen(function* () {\n const attempt =\n typeof approval === \"string\"\n ? yield* storage.attempts.byApproval(approval)\n : yield* attemptOf(approval.planId);\n if (attempt.kind !== kind) {\n return yield* Errors.fail(\n new Reason.NotFound({\n entity: \"approval\",\n id: typeof approval === \"string\" ? approval : approval.id,\n }),\n );\n }\n const stored = attempt.approval;\n if (stored === null || (typeof approval !== \"string\" && stored.id !== approval.id)) {\n return yield* Errors.fail(\n new Reason.NotFound({\n entity: \"approval\",\n id: typeof approval === \"string\" ? approval : approval.id,\n }),\n );\n }\n if (attempt.receipt !== null) return attempt.receipt;\n const now = yield* DateTime.now;\n if (past(stored.expiresAt, now)) return yield* expired(\"approval\", stored.id);\n const claimed = yield* storage.attempts.claim(\n attempt.id,\n DateTime.addDuration(now, Duration.millis(policy.applyLeaseMs)),\n );\n return yield* run(claimed, stored).pipe(\n Effect.tapError((error) =>\n storage.attempts.fail(claimed.id, error.message).pipe(Effect.ignore),\n ),\n );\n });\n\n /** Writes under a claimed lease; a failure before any write releases the attempt as `failed`. */\n const run = (attempt: Storage.Attempt, approval: Approval.Model): Fx<Receipt.Model> =>\n Effect.gen(function* () {\n const plan = attempt.plan;\n const attachment = yield* storage.attachments.get(attempt.attachmentId);\n const { session, target } = yield* connect.session(attachment.id);\n const dns = session.dns(target);\n yield* revalidate(plan, dns, target.zone);\n const approved = new Set(approval.operationIds);\n const outcomes: Array<Receipt.Outcome> = [];\n const applied: Array<Receipt.Applied> = [];\n let stopped = false;\n const finish = (status: \"complete\" | \"partial\") =>\n Effect.gen(function* () {\n const receipt = new Receipt.Model({\n id: Receipt.ReceiptId.make(yield* fresh(\"rcpt\")),\n version: \"domainkit.receipt.v2\",\n kind,\n planId: plan.id,\n approvalId: approval.id,\n digest: plan.digest,\n provider: plan.provider,\n zone: plan.zone,\n status,\n outcomes: [...outcomes],\n appliedAt: yield* DateTime.now,\n });\n yield* storage.attempts.complete(attempt.id, receipt);\n return receipt;\n });\n const writes = Effect.gen(function* () {\n for (const operation of plan.operations) {\n if (operation._tag === \"Noop\") {\n outcomes.push(new Receipt.Skipped({ operationId: operation.id, reason: \"noop\" }));\n continue;\n }\n if (operation._tag === \"Conflict\" || !approved.has(operation.id)) {\n outcomes.push(\n new Receipt.Skipped({ operationId: operation.id, reason: \"not-approved\" }),\n );\n continue;\n }\n if (stopped) {\n outcomes.push(\n new Receipt.Skipped({ operationId: operation.id, reason: \"not-attempted\" }),\n );\n continue;\n }\n const result = yield* write(plan, operation, dns, target.zone).pipe(Effect.result);\n if (result._tag === \"Success\") {\n const outcome = new Receipt.Applied({\n operationId: operation.id,\n providerRecordId: result.success,\n });\n outcomes.push(outcome);\n applied.push(outcome);\n continue;\n }\n if (applied.length === 0) return yield* Effect.fail(result.failure);\n stopped = true;\n outcomes.push(\n new Receipt.Failed({ operationId: operation.id, message: result.failure.message }),\n );\n }\n return yield* finish(stopped ? \"partial\" : \"complete\");\n });\n return yield* writes.pipe(\n Effect.onInterrupt(() =>\n applied.length === 0\n ? Effect.void\n : Effect.gen(function* () {\n for (const operation of plan.operations) {\n if (outcomes.some((outcome) => outcome.operationId === operation.id)) continue;\n outcomes.push(\n new Receipt.Skipped({ operationId: operation.id, reason: \"not-attempted\" }),\n );\n }\n yield* finish(\"partial\");\n }).pipe(Effect.ignore),\n ),\n );\n });\n\n /** Re-plan the zone and fail `Stale` when the digest moved since the plan was built. */\n const revalidate = (plan: Plan.Model, dns: Provider.Dns, zone: string) =>\n Effect.gen(function* () {\n const current = yield* currentOperations(plan, dns, zone);\n const digest = yield* Planner.digest({\n version: plan.version,\n kind: plan.kind,\n domain: plan.domain,\n zone: plan.zone,\n provider: plan.provider,\n attachmentId: plan.attachmentId,\n operations: current,\n });\n if (digest !== plan.digest) return yield* stale(plan);\n });\n\n const currentOperations = (\n plan: Plan.Model,\n dns: Provider.Dns,\n zone: string,\n ): Effect.Effect<ReadonlyArray<Plan.Operation>, Errors.DomainKitError> =>\n kind === \"provisioning\"\n ? Effect.flatMap(dns.list(zone), (observed) =>\n Planner.reconcile(\n Planner.requirements(plan),\n observed.map(({ record: observedRecord }) => observedRecord),\n ),\n )\n : Effect.forEach(\n plan.operations,\n (operation): Effect.Effect<Plan.Operation, Errors.DomainKitError> =>\n operation._tag === \"Delete\"\n ? cleanupOperation(\n dns,\n zone,\n operation.id,\n operation.record,\n operation.providerRecordId,\n )\n : Effect.succeed(operation),\n );\n\n const write = (\n plan: Plan.Model,\n operation: Plan.Create | Plan.Delete,\n dns: Provider.Dns,\n zone: string,\n ): Effect.Effect<string, Errors.DomainKitError> =>\n operation._tag === \"Create\"\n ? Effect.gen(function* () {\n const observed = yield* dns.list(zone);\n const [check] = yield* Planner.reconcile(\n [operation.record],\n observed.map(({ record: observedRecord }) => observedRecord),\n );\n if (check?._tag !== \"Create\") return yield* stale(plan);\n const created = yield* dns.create(zone, operation.record);\n return created.providerRecordId;\n })\n : Effect.gen(function* () {\n const check = yield* cleanupOperation(\n dns,\n zone,\n operation.id,\n operation.record,\n operation.providerRecordId,\n );\n if (check._tag !== \"Delete\") return yield* stale(plan);\n yield* dns.delete(zone, operation.providerRecordId);\n return operation.providerRecordId;\n });\n\n return { record, approve, reject, apply, attemptOf };\n};\n\n/** Read one receipt-proven record back; anything but an exact match is a conflict. */\nexport const cleanupOperation = (\n dns: Provider.Dns,\n zone: string,\n id: Plan.OperationId,\n record: DnsRecord.Model,\n providerRecordId: string,\n): Effect.Effect<Plan.Delete | Plan.Conflict, Errors.DomainKitError> =>\n Effect.map(dns.get(zone, providerRecordId), (observed) => {\n if (observed === null)\n return new Plan.Conflict({ id, record, existing: [], reason: \"missing\" });\n if (!DnsRecord.equals(observed, record)) {\n return new Plan.Conflict({ id, record, existing: [observed], reason: \"value-mismatch\" });\n }\n return new Plan.Delete({ id, record, providerRecordId });\n });\n"],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { NotFound, ProviderConflict, Unauthenticated } from "../../Reason.mjs";
|
|
2
|
+
import { decode, fail } from "../error.mjs";
|
|
3
|
+
import { bearer, classify, rejected, requestJson } from "../http.mjs";
|
|
4
|
+
import { BaseEnvelope, Envelope, Record, Token, Zone } from "./protocol.mjs";
|
|
5
|
+
import { decode as decode$1, encode, isWithinZone } from "./records.mjs";
|
|
6
|
+
import { DateTime, Effect, Schema } from "effect";
|
|
7
|
+
//#region src/internal/cloudflare/client.ts
|
|
8
|
+
const provider = "cloudflare";
|
|
9
|
+
const notFound = new NotFound({
|
|
10
|
+
entity: "zone",
|
|
11
|
+
id: "cloudflare"
|
|
12
|
+
});
|
|
13
|
+
/** Cloudflare's "record already exists" family (81056 identical, 81057 same name and content, 81058 CNAME). */
|
|
14
|
+
const conflictCodes = /* @__PURE__ */ new Set([
|
|
15
|
+
81056,
|
|
16
|
+
81057,
|
|
17
|
+
81058
|
|
18
|
+
]);
|
|
19
|
+
const call = (options, path, result, init) => Effect.gen(function* () {
|
|
20
|
+
const reply = yield* requestJson({
|
|
21
|
+
fetch: options.fetch,
|
|
22
|
+
provider,
|
|
23
|
+
url: `${options.baseUrl}${path}`,
|
|
24
|
+
init: bearer(options.token, init)
|
|
25
|
+
});
|
|
26
|
+
if (reply.status === 404) return yield* fail(notFound);
|
|
27
|
+
const base = Schema.decodeUnknownOption(BaseEnvelope)(reply.body);
|
|
28
|
+
if (!reply.ok || base._tag === "None" || !base.value.success) {
|
|
29
|
+
const detail = base._tag === "Some" ? base.value.errors[0] : void 0;
|
|
30
|
+
if (detail !== void 0 && conflictCodes.has(detail.code)) return yield* fail(new ProviderConflict({
|
|
31
|
+
provider,
|
|
32
|
+
code: String(detail.code),
|
|
33
|
+
message: detail.message
|
|
34
|
+
}));
|
|
35
|
+
return yield* fail(classify(provider, reply.status, reply.headers, detail?.message ?? `Cloudflare request failed with HTTP ${reply.status}`, detail === void 0 ? void 0 : String(detail.code)));
|
|
36
|
+
}
|
|
37
|
+
const contract = () => rejected(provider, "Cloudflare response did not match its API contract", "response");
|
|
38
|
+
const envelope = yield* decode(Envelope, reply.body).pipe(Effect.mapError(contract));
|
|
39
|
+
return {
|
|
40
|
+
result: yield* decode(result, envelope.result).pipe(Effect.mapError(contract)),
|
|
41
|
+
totalPages: envelope.result_info?.total_pages
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
const paginate = (options, path, result, perPage) => Effect.gen(function* () {
|
|
45
|
+
const rows = [];
|
|
46
|
+
let page = 1;
|
|
47
|
+
while (true) {
|
|
48
|
+
const reply = yield* call(options, path(page), Schema.Array(result));
|
|
49
|
+
rows.push(...reply.result);
|
|
50
|
+
if (reply.totalPages === void 0 ? reply.result.length < perPage : page >= reply.totalPages) return rows;
|
|
51
|
+
page += 1;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
/** Every zone the credential can see, across accounts; internal zones cannot host records. */
|
|
55
|
+
const listZones = (options, accountId) => paginate(options, (page) => {
|
|
56
|
+
const query = new URLSearchParams({
|
|
57
|
+
page: String(page),
|
|
58
|
+
per_page: "50"
|
|
59
|
+
});
|
|
60
|
+
if (accountId !== void 0) query.set("account.id", accountId);
|
|
61
|
+
return `/zones?${query}`;
|
|
62
|
+
}, Zone, 50).pipe(Effect.map((zones) => zones.filter((zone) => zone.type !== "internal")));
|
|
63
|
+
/** Token expiry from the verify endpoint; `null` when the token never expires or verification is unavailable. */
|
|
64
|
+
/** Token status and expiry from a verify endpoint; an inactive token is `Unauthenticated`. */
|
|
65
|
+
const verifyToken = (options, path) => call(options, path, Token).pipe(Effect.flatMap(({ result }) => result.status === "active" ? Effect.succeed(typeof result.expires_on === "string" ? DateTime.makeUnsafe(result.expires_on) : null) : fail(new Unauthenticated({ message: `Cloudflare token is ${result.status}` }))));
|
|
66
|
+
const tokenExpiry = (options, accountId) => {
|
|
67
|
+
const verify = (path) => call(options, path, Token).pipe(Effect.flatMap(({ result }) => result.status === "active" ? Effect.succeed(typeof result.expires_on === "string" ? DateTime.makeUnsafe(result.expires_on) : null) : fail(new Unauthenticated({ message: `Cloudflare token is ${result.status}` }))));
|
|
68
|
+
return verify("/user/tokens/verify").pipe(Effect.map((expiresAt) => ({
|
|
69
|
+
expiresAt,
|
|
70
|
+
kind: "user"
|
|
71
|
+
})), Effect.catchIf((error) => error.reason._tag !== "Unauthenticated" || accountId !== null, () => accountId === null ? Effect.succeed({
|
|
72
|
+
expiresAt: null,
|
|
73
|
+
kind: "user"
|
|
74
|
+
}) : verify(`/accounts/${encodeURIComponent(accountId)}/tokens/verify`).pipe(Effect.map((expiresAt) => ({
|
|
75
|
+
expiresAt,
|
|
76
|
+
kind: "account"
|
|
77
|
+
})), Effect.catchIf((error) => error.reason._tag !== "Unauthenticated", () => Effect.succeed({
|
|
78
|
+
expiresAt: null,
|
|
79
|
+
kind: "user"
|
|
80
|
+
})))));
|
|
81
|
+
};
|
|
82
|
+
const dns = (options, zoneId) => {
|
|
83
|
+
const base = `/zones/${encodeURIComponent(zoneId)}/dns_records`;
|
|
84
|
+
return {
|
|
85
|
+
list: (zone) => paginate(options, (page) => `${base}?${new URLSearchParams({
|
|
86
|
+
page: String(page),
|
|
87
|
+
per_page: "100"
|
|
88
|
+
})}`, Record, 100).pipe(Effect.flatMap((records) => Effect.forEach(records.filter((record) => isWithinZone(record.name, zone)), decode$1))),
|
|
89
|
+
create: (_zone, record) => call(options, base, Record, {
|
|
90
|
+
method: "POST",
|
|
91
|
+
body: JSON.stringify(encode(record))
|
|
92
|
+
}).pipe(Effect.map(({ result }) => ({ providerRecordId: result.id }))),
|
|
93
|
+
get: (_zone, providerRecordId) => call(options, `${base}/${encodeURIComponent(providerRecordId)}`, Record).pipe(Effect.flatMap(({ result }) => decode$1(result)), Effect.map(({ record }) => record), Effect.catchIf((error) => error.reason === notFound, () => Effect.succeed(null))),
|
|
94
|
+
delete: (_zone, providerRecordId) => call(options, `${base}/${encodeURIComponent(providerRecordId)}`, Schema.Unknown, { method: "DELETE" }).pipe(Effect.asVoid)
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
//#endregion
|
|
98
|
+
export { dns, listZones, provider, tokenExpiry, verifyToken };
|
|
99
|
+
|
|
100
|
+
//# sourceMappingURL=client.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.mjs","names":["Reason.NotFound","Errors.fail","S","Protocol.BaseEnvelope","Reason.ProviderConflict","Errors.decode","Protocol.Envelope","Protocol.Zone","Protocol.Token","Reason.Unauthenticated","Protocol.Record","Records.isWithinZone","Records.decode","Records.encode"],"sources":["../../../src/internal/cloudflare/client.ts"],"sourcesContent":["import { DateTime, Effect, Schema as S } from \"effect\";\n\nimport type * as DnsRecord from \"../../DnsRecord.ts\";\nimport * as Errors from \"../error.ts\";\nimport * as Reason from \"../../Reason.ts\";\nimport type * as Provider from \"../../Provider.ts\";\nimport { bearer, classify, type Fetch, rejected, requestJson } from \"../http.ts\";\nimport * as Protocol from \"./protocol.ts\";\nimport * as Records from \"./records.ts\";\n\nexport const provider = \"cloudflare\";\n\nconst notFound = new Reason.NotFound({ entity: \"zone\", id: \"cloudflare\" });\n\n/** Cloudflare's \"record already exists\" family (81056 identical, 81057 same name and content, 81058 CNAME). */\nconst conflictCodes = new Set([81056, 81057, 81058]);\n\nexport interface Options {\n readonly token: string;\n readonly fetch: Fetch;\n readonly baseUrl: string;\n}\n\ntype Fx<A> = Effect.Effect<A, Errors.DomainKitError>;\n\nconst call = <R extends S.ConstraintDecoder<unknown>>(\n options: Options,\n path: string,\n result: R,\n init?: RequestInit,\n): Fx<{ readonly result: R[\"Type\"]; readonly totalPages: number | undefined }> =>\n Effect.gen(function* () {\n const reply = yield* requestJson({\n fetch: options.fetch,\n provider,\n url: `${options.baseUrl}${path}`,\n init: bearer(options.token, init),\n });\n if (reply.status === 404) return yield* Errors.fail(notFound);\n const base = S.decodeUnknownOption(Protocol.BaseEnvelope)(reply.body);\n if (!reply.ok || base._tag === \"None\" || !base.value.success) {\n const detail = base._tag === \"Some\" ? base.value.errors[0] : undefined;\n if (detail !== undefined && conflictCodes.has(detail.code)) {\n return yield* Errors.fail(\n new Reason.ProviderConflict({\n provider,\n code: String(detail.code),\n message: detail.message,\n }),\n );\n }\n return yield* Errors.fail(\n classify(\n provider,\n reply.status,\n reply.headers,\n detail?.message ?? `Cloudflare request failed with HTTP ${reply.status}`,\n detail === undefined ? undefined : String(detail.code),\n ),\n );\n }\n const contract = () =>\n rejected(provider, \"Cloudflare response did not match its API contract\", \"response\");\n const envelope = yield* Errors.decode(Protocol.Envelope, reply.body).pipe(\n Effect.mapError(contract),\n );\n const decoded: R[\"Type\"] = yield* Errors.decode(result, envelope.result).pipe(\n Effect.mapError(contract),\n );\n return { result: decoded, totalPages: envelope.result_info?.total_pages };\n });\n\nconst paginate = <R extends S.ConstraintDecoder<unknown>>(\n options: Options,\n path: (page: number) => string,\n result: R,\n perPage: number,\n): Fx<ReadonlyArray<R[\"Type\"]>> =>\n Effect.gen(function* () {\n const rows: Array<R[\"Type\"]> = [];\n let page = 1;\n while (true) {\n const reply = yield* call(options, path(page), S.Array(result));\n rows.push(...reply.result);\n const last =\n reply.totalPages === undefined ? reply.result.length < perPage : page >= reply.totalPages;\n if (last) return rows;\n page += 1;\n }\n });\n\n/** Every zone the credential can see, across accounts; internal zones cannot host records. */\nexport const listZones = (options: Options, accountId?: string): Fx<ReadonlyArray<Protocol.Zone>> =>\n paginate(\n options,\n (page) => {\n const query = new URLSearchParams({ page: String(page), per_page: \"50\" });\n if (accountId !== undefined) query.set(\"account.id\", accountId);\n return `/zones?${query}`;\n },\n Protocol.Zone,\n 50,\n ).pipe(Effect.map((zones) => zones.filter((zone) => zone.type !== \"internal\")));\n\n/** Token expiry from the verify endpoint; `null` when the token never expires or verification is unavailable. */\n/** Token status and expiry from a verify endpoint; an inactive token is `Unauthenticated`. */\nexport const verifyToken = (options: Options, path: string): Fx<DateTime.Utc | null> =>\n call(options, path, Protocol.Token).pipe(\n Effect.flatMap(({ result }) =>\n result.status === \"active\"\n ? Effect.succeed(\n typeof result.expires_on === \"string\" ? DateTime.makeUnsafe(result.expires_on) : null,\n )\n : Errors.fail(\n new Reason.Unauthenticated({\n message: `Cloudflare token is ${result.status}`,\n }),\n ),\n ),\n );\n\nexport const tokenExpiry = (\n options: Options,\n accountId: string | null,\n): Fx<{ readonly expiresAt: DateTime.Utc | null; readonly kind: \"user\" | \"account\" }> => {\n const verify = (path: string) =>\n call(options, path, Protocol.Token).pipe(\n Effect.flatMap(({ result }) =>\n result.status === \"active\"\n ? Effect.succeed(\n typeof result.expires_on === \"string\" ? DateTime.makeUnsafe(result.expires_on) : null,\n )\n : Errors.fail(\n new Reason.Unauthenticated({\n message: `Cloudflare token is ${result.status}`,\n }),\n ),\n ),\n );\n return verify(\"/user/tokens/verify\").pipe(\n Effect.map((expiresAt) => ({ expiresAt, kind: \"user\" as const })),\n Effect.catchIf(\n (error) => error.reason._tag !== \"Unauthenticated\" || accountId !== null,\n () =>\n accountId === null\n ? Effect.succeed({ expiresAt: null, kind: \"user\" as const })\n : verify(`/accounts/${encodeURIComponent(accountId)}/tokens/verify`).pipe(\n Effect.map((expiresAt) => ({ expiresAt, kind: \"account\" as const })),\n Effect.catchIf(\n (error) => error.reason._tag !== \"Unauthenticated\",\n () => Effect.succeed({ expiresAt: null, kind: \"user\" as const }),\n ),\n ),\n ),\n );\n};\n\nexport const dns = (options: Options, zoneId: string): Provider.Dns => {\n const base = `/zones/${encodeURIComponent(zoneId)}/dns_records`;\n return {\n list: (zone) =>\n paginate(\n options,\n (page) => `${base}?${new URLSearchParams({ page: String(page), per_page: \"100\" })}`,\n Protocol.Record,\n 100,\n ).pipe(\n Effect.flatMap((records) =>\n Effect.forEach(\n records.filter((record) => Records.isWithinZone(record.name, zone)),\n Records.decode,\n ),\n ),\n ),\n create: (_zone, record: DnsRecord.Model) =>\n call(options, base, Protocol.Record, {\n method: \"POST\",\n body: JSON.stringify(Records.encode(record)),\n }).pipe(Effect.map(({ result }) => ({ providerRecordId: result.id }))),\n get: (_zone, providerRecordId) =>\n call(options, `${base}/${encodeURIComponent(providerRecordId)}`, Protocol.Record).pipe(\n Effect.flatMap(({ result }) => Records.decode(result)),\n Effect.map(({ record }) => record),\n Effect.catchIf(\n (error) => error.reason === notFound,\n () => Effect.succeed(null),\n ),\n ),\n delete: (_zone, providerRecordId) =>\n call(options, `${base}/${encodeURIComponent(providerRecordId)}`, S.Unknown, {\n method: \"DELETE\",\n }).pipe(Effect.asVoid),\n };\n};\n"],"mappings":";;;;;;;AAUA,MAAa,WAAW;AAExB,MAAM,WAAW,IAAIA,SAAgB;CAAE,QAAQ;CAAQ,IAAI;AAAa,CAAC;;AAGzE,MAAM,gCAAgB,IAAI,IAAI;CAAC;CAAO;CAAO;AAAK,CAAC;AAUnD,MAAM,QACJ,SACA,MACA,QACA,SAEA,OAAO,IAAI,aAAa;CACtB,MAAM,QAAQ,OAAO,YAAY;EAC/B,OAAO,QAAQ;EACf;EACA,KAAK,GAAG,QAAQ,UAAU;EAC1B,MAAM,OAAO,QAAQ,OAAO,IAAI;CAClC,CAAC;CACD,IAAI,MAAM,WAAW,KAAK,OAAO,OAAOC,KAAY,QAAQ;CAC5D,MAAM,OAAOC,OAAE,oBAAoBC,YAAqB,CAAC,CAAC,MAAM,IAAI;CACpE,IAAI,CAAC,MAAM,MAAM,KAAK,SAAS,UAAU,CAAC,KAAK,MAAM,SAAS;EAC5D,MAAM,SAAS,KAAK,SAAS,SAAS,KAAK,MAAM,OAAO,KAAK,KAAA;EAC7D,IAAI,WAAW,KAAA,KAAa,cAAc,IAAI,OAAO,IAAI,GACvD,OAAO,OAAOF,KACZ,IAAIG,iBAAwB;GAC1B;GACA,MAAM,OAAO,OAAO,IAAI;GACxB,SAAS,OAAO;EAClB,CAAC,CACH;EAEF,OAAO,OAAOH,KACZ,SACE,UACA,MAAM,QACN,MAAM,SACN,QAAQ,WAAW,uCAAuC,MAAM,UAChE,WAAW,KAAA,IAAY,KAAA,IAAY,OAAO,OAAO,IAAI,CACvD,CACF;CACF;CACA,MAAM,iBACJ,SAAS,UAAU,sDAAsD,UAAU;CACrF,MAAM,WAAW,OAAOI,OAAcC,UAAmB,MAAM,IAAI,CAAC,CAAC,KACnE,OAAO,SAAS,QAAQ,CAC1B;CAIA,OAAO;EAAE,QAAQ,OAHiBD,OAAc,QAAQ,SAAS,MAAM,CAAC,CAAC,KACvE,OAAO,SAAS,QAAQ,CAC1B;EAC0B,YAAY,SAAS,aAAa;CAAY;AAC1E,CAAC;AAEH,MAAM,YACJ,SACA,MACA,QACA,YAEA,OAAO,IAAI,aAAa;CACtB,MAAM,OAAyB,CAAC;CAChC,IAAI,OAAO;CACX,OAAO,MAAM;EACX,MAAM,QAAQ,OAAO,KAAK,SAAS,KAAK,IAAI,GAAGH,OAAE,MAAM,MAAM,CAAC;EAC9D,KAAK,KAAK,GAAG,MAAM,MAAM;EAGzB,IADE,MAAM,eAAe,KAAA,IAAY,MAAM,OAAO,SAAS,UAAU,QAAQ,MAAM,YACvE,OAAO;EACjB,QAAQ;CACV;AACF,CAAC;;AAGH,MAAa,aAAa,SAAkB,cAC1C,SACE,UACC,SAAS;CACR,MAAM,QAAQ,IAAI,gBAAgB;EAAE,MAAM,OAAO,IAAI;EAAG,UAAU;CAAK,CAAC;CACxE,IAAI,cAAc,KAAA,GAAW,MAAM,IAAI,cAAc,SAAS;CAC9D,OAAO,UAAU;AACnB,GACAK,MACA,EACF,CAAC,CAAC,KAAK,OAAO,KAAK,UAAU,MAAM,QAAQ,SAAS,KAAK,SAAS,UAAU,CAAC,CAAC;;;AAIhF,MAAa,eAAe,SAAkB,SAC5C,KAAK,SAAS,MAAMC,KAAc,CAAC,CAAC,KAClC,OAAO,SAAS,EAAE,aAChB,OAAO,WAAW,WACd,OAAO,QACL,OAAO,OAAO,eAAe,WAAW,SAAS,WAAW,OAAO,UAAU,IAAI,IACnF,IACAP,KACE,IAAIQ,gBAAuB,EACzB,SAAS,uBAAuB,OAAO,SACzC,CAAC,CACH,CACN,CACF;AAEF,MAAa,eACX,SACA,cACuF;CACvF,MAAM,UAAU,SACd,KAAK,SAAS,MAAMD,KAAc,CAAC,CAAC,KAClC,OAAO,SAAS,EAAE,aAChB,OAAO,WAAW,WACd,OAAO,QACL,OAAO,OAAO,eAAe,WAAW,SAAS,WAAW,OAAO,UAAU,IAAI,IACnF,IACAP,KACE,IAAIQ,gBAAuB,EACzB,SAAS,uBAAuB,OAAO,SACzC,CAAC,CACH,CACN,CACF;CACF,OAAO,OAAO,qBAAqB,CAAC,CAAC,KACnC,OAAO,KAAK,eAAe;EAAE;EAAW,MAAM;CAAgB,EAAE,GAChE,OAAO,SACJ,UAAU,MAAM,OAAO,SAAS,qBAAqB,cAAc,YAElE,cAAc,OACV,OAAO,QAAQ;EAAE,WAAW;EAAM,MAAM;CAAgB,CAAC,IACzD,OAAO,aAAa,mBAAmB,SAAS,EAAE,eAAe,CAAC,CAAC,KACjE,OAAO,KAAK,eAAe;EAAE;EAAW,MAAM;CAAmB,EAAE,GACnE,OAAO,SACJ,UAAU,MAAM,OAAO,SAAS,yBAC3B,OAAO,QAAQ;EAAE,WAAW;EAAM,MAAM;CAAgB,CAAC,CACjE,CACF,CACR,CACF;AACF;AAEA,MAAa,OAAO,SAAkB,WAAiC;CACrE,MAAM,OAAO,UAAU,mBAAmB,MAAM,EAAE;CAClD,OAAO;EACL,OAAO,SACL,SACE,UACC,SAAS,GAAG,KAAK,GAAG,IAAI,gBAAgB;GAAE,MAAM,OAAO,IAAI;GAAG,UAAU;EAAM,CAAC,KAChFC,QACA,GACF,CAAC,CAAC,KACA,OAAO,SAAS,YACd,OAAO,QACL,QAAQ,QAAQ,WAAWC,aAAqB,OAAO,MAAM,IAAI,CAAC,GAClEC,QACF,CACF,CACF;EACF,SAAS,OAAO,WACd,KAAK,SAAS,MAAMF,QAAiB;GACnC,QAAQ;GACR,MAAM,KAAK,UAAUG,OAAe,MAAM,CAAC;EAC7C,CAAC,CAAC,CAAC,KAAK,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,OAAO,GAAG,EAAE,CAAC;EACvE,MAAM,OAAO,qBACX,KAAK,SAAS,GAAG,KAAK,GAAG,mBAAmB,gBAAgB,KAAKH,MAAe,CAAC,CAAC,KAChF,OAAO,SAAS,EAAE,aAAaE,SAAe,MAAM,CAAC,GACrD,OAAO,KAAK,EAAE,aAAa,MAAM,GACjC,OAAO,SACJ,UAAU,MAAM,WAAW,gBACtB,OAAO,QAAQ,IAAI,CAC3B,CACF;EACF,SAAS,OAAO,qBACd,KAAK,SAAS,GAAG,KAAK,GAAG,mBAAmB,gBAAgB,KAAKV,OAAE,SAAS,EAC1E,QAAQ,SACV,CAAC,CAAC,CAAC,KAAK,OAAO,MAAM;CACzB;AACF"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
//#region src/internal/cloudflare/protocol.ts
|
|
3
|
+
const ErrorDetail = Schema.Struct({
|
|
4
|
+
code: Schema.Number,
|
|
5
|
+
message: Schema.String
|
|
6
|
+
});
|
|
7
|
+
const Message = Schema.Struct({
|
|
8
|
+
code: Schema.optionalKey(Schema.Number),
|
|
9
|
+
message: Schema.String
|
|
10
|
+
});
|
|
11
|
+
const ResultInfo = Schema.Struct({
|
|
12
|
+
count: Schema.optionalKey(Schema.Number),
|
|
13
|
+
page: Schema.optionalKey(Schema.Number),
|
|
14
|
+
per_page: Schema.optionalKey(Schema.Number),
|
|
15
|
+
total_count: Schema.optionalKey(Schema.Number),
|
|
16
|
+
total_pages: Schema.optionalKey(Schema.Number)
|
|
17
|
+
});
|
|
18
|
+
const BaseEnvelope = Schema.Struct({
|
|
19
|
+
errors: Schema.Array(ErrorDetail),
|
|
20
|
+
messages: Schema.optionalKey(Schema.Array(Message)),
|
|
21
|
+
success: Schema.Boolean
|
|
22
|
+
});
|
|
23
|
+
const Account = Schema.Struct({
|
|
24
|
+
id: Schema.String,
|
|
25
|
+
name: Schema.String
|
|
26
|
+
});
|
|
27
|
+
const Zone = Schema.Struct({
|
|
28
|
+
account: Account,
|
|
29
|
+
id: Schema.String,
|
|
30
|
+
name: Schema.String,
|
|
31
|
+
name_servers: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
32
|
+
status: Schema.optionalKey(Schema.String),
|
|
33
|
+
type: Schema.optionalKey(Schema.String)
|
|
34
|
+
});
|
|
35
|
+
const Record = Schema.Struct({
|
|
36
|
+
content: Schema.optionalKey(Schema.String),
|
|
37
|
+
data: Schema.optionalKey(Schema.Unknown),
|
|
38
|
+
id: Schema.String,
|
|
39
|
+
name: Schema.String,
|
|
40
|
+
priority: Schema.optionalKey(Schema.Number),
|
|
41
|
+
proxied: Schema.optionalKey(Schema.Boolean),
|
|
42
|
+
ttl: Schema.Number,
|
|
43
|
+
type: Schema.String
|
|
44
|
+
});
|
|
45
|
+
const Token = Schema.Struct({
|
|
46
|
+
expires_on: Schema.optionalKey(Schema.NullOr(Schema.String)),
|
|
47
|
+
id: Schema.String,
|
|
48
|
+
status: Schema.String
|
|
49
|
+
});
|
|
50
|
+
const Envelope = Schema.Struct({
|
|
51
|
+
errors: Schema.Array(ErrorDetail),
|
|
52
|
+
result: Schema.Unknown,
|
|
53
|
+
result_info: Schema.optionalKey(ResultInfo),
|
|
54
|
+
success: Schema.Boolean
|
|
55
|
+
});
|
|
56
|
+
//#endregion
|
|
57
|
+
export { Account, BaseEnvelope, Envelope, ErrorDetail, Record, ResultInfo, Token, Zone };
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=protocol.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.mjs","names":["S"],"sources":["../../../src/internal/cloudflare/protocol.ts"],"sourcesContent":["import { Schema as S } from \"effect\";\n\nexport const ErrorDetail = S.Struct({ code: S.Number, message: S.String });\n\nconst Message = S.Struct({ code: S.optionalKey(S.Number), message: S.String });\n\nexport const ResultInfo = S.Struct({\n count: S.optionalKey(S.Number),\n page: S.optionalKey(S.Number),\n per_page: S.optionalKey(S.Number),\n total_count: S.optionalKey(S.Number),\n total_pages: S.optionalKey(S.Number),\n});\n\nexport const BaseEnvelope = S.Struct({\n errors: S.Array(ErrorDetail),\n messages: S.optionalKey(S.Array(Message)),\n success: S.Boolean,\n});\n\nexport const Account = S.Struct({ id: S.String, name: S.String });\nexport type Account = typeof Account.Type;\n\nexport const Zone = S.Struct({\n account: Account,\n id: S.String,\n name: S.String,\n name_servers: S.optionalKey(S.Array(S.String)),\n status: S.optionalKey(S.String),\n type: S.optionalKey(S.String),\n});\nexport type Zone = typeof Zone.Type;\n\nexport const Record = S.Struct({\n content: S.optionalKey(S.String),\n data: S.optionalKey(S.Unknown),\n id: S.String,\n name: S.String,\n priority: S.optionalKey(S.Number),\n proxied: S.optionalKey(S.Boolean),\n ttl: S.Number,\n type: S.String,\n});\nexport type Record = typeof Record.Type;\n\nexport const Token = S.Struct({\n expires_on: S.optionalKey(S.NullOr(S.String)),\n id: S.String,\n status: S.String,\n});\n\nexport const Envelope = S.Struct({\n errors: S.Array(ErrorDetail),\n result: S.Unknown,\n result_info: S.optionalKey(ResultInfo),\n success: S.Boolean,\n});\n"],"mappings":";;AAEA,MAAa,cAAcA,OAAE,OAAO;CAAE,MAAMA,OAAE;CAAQ,SAASA,OAAE;AAAO,CAAC;AAEzE,MAAM,UAAUA,OAAE,OAAO;CAAE,MAAMA,OAAE,YAAYA,OAAE,MAAM;CAAG,SAASA,OAAE;AAAO,CAAC;AAE7E,MAAa,aAAaA,OAAE,OAAO;CACjC,OAAOA,OAAE,YAAYA,OAAE,MAAM;CAC7B,MAAMA,OAAE,YAAYA,OAAE,MAAM;CAC5B,UAAUA,OAAE,YAAYA,OAAE,MAAM;CAChC,aAAaA,OAAE,YAAYA,OAAE,MAAM;CACnC,aAAaA,OAAE,YAAYA,OAAE,MAAM;AACrC,CAAC;AAED,MAAa,eAAeA,OAAE,OAAO;CACnC,QAAQA,OAAE,MAAM,WAAW;CAC3B,UAAUA,OAAE,YAAYA,OAAE,MAAM,OAAO,CAAC;CACxC,SAASA,OAAE;AACb,CAAC;AAED,MAAa,UAAUA,OAAE,OAAO;CAAE,IAAIA,OAAE;CAAQ,MAAMA,OAAE;AAAO,CAAC;AAGhE,MAAa,OAAOA,OAAE,OAAO;CAC3B,SAAS;CACT,IAAIA,OAAE;CACN,MAAMA,OAAE;CACR,cAAcA,OAAE,YAAYA,OAAE,MAAMA,OAAE,MAAM,CAAC;CAC7C,QAAQA,OAAE,YAAYA,OAAE,MAAM;CAC9B,MAAMA,OAAE,YAAYA,OAAE,MAAM;AAC9B,CAAC;AAGD,MAAa,SAASA,OAAE,OAAO;CAC7B,SAASA,OAAE,YAAYA,OAAE,MAAM;CAC/B,MAAMA,OAAE,YAAYA,OAAE,OAAO;CAC7B,IAAIA,OAAE;CACN,MAAMA,OAAE;CACR,UAAUA,OAAE,YAAYA,OAAE,MAAM;CAChC,SAASA,OAAE,YAAYA,OAAE,OAAO;CAChC,KAAKA,OAAE;CACP,MAAMA,OAAE;AACV,CAAC;AAGD,MAAa,QAAQA,OAAE,OAAO;CAC5B,YAAYA,OAAE,YAAYA,OAAE,OAAOA,OAAE,MAAM,CAAC;CAC5C,IAAIA,OAAE;CACN,QAAQA,OAAE;AACZ,CAAC;AAED,MAAa,WAAWA,OAAE,OAAO;CAC/B,QAAQA,OAAE,MAAM,WAAW;CAC3B,QAAQA,OAAE;CACV,aAAaA,OAAE,YAAY,UAAU;CACrC,SAASA,OAAE;AACb,CAAC"}
|