domainkit 0.1.0 → 0.2.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 (68) hide show
  1. package/README.md +91 -71
  2. package/dist/{auth-Vi-hhXbV.mjs → auth-CZ1pZHMZ.mjs} +301 -38
  3. package/dist/auth-CZ1pZHMZ.mjs.map +1 -0
  4. package/dist/{client-BnD7QLRF.mjs → auth-CzhZ3Cuc.mjs} +235 -131
  5. package/dist/auth-CzhZ3Cuc.mjs.map +1 -0
  6. package/dist/authorization-DOg2Re1D.mjs +247 -0
  7. package/dist/authorization-DOg2Re1D.mjs.map +1 -0
  8. package/dist/canonical-json-CpvqGMcr.mjs +57 -0
  9. package/dist/canonical-json-CpvqGMcr.mjs.map +1 -0
  10. package/dist/{cloudflare-C6ryPlCZ.mjs → cloudflare-BedFw4cX.mjs} +28 -7
  11. package/dist/cloudflare-BedFw4cX.mjs.map +1 -0
  12. package/dist/cloudflare-FDl5q7Dm.d.mts +32 -0
  13. package/dist/cloudflare.d.mts +3 -3
  14. package/dist/cloudflare.mjs +2 -2
  15. package/dist/connection-BWrBwqjk.mjs +58 -0
  16. package/dist/connection-BWrBwqjk.mjs.map +1 -0
  17. package/dist/connection-CEqadXSm.d.mts +182 -0
  18. package/dist/connection-CcQokgrd.mjs +186 -0
  19. package/dist/connection-CcQokgrd.mjs.map +1 -0
  20. package/dist/connection-api-DftK7eHo.d.mts +7 -0
  21. package/dist/{dns-record-C_KwGPLf.mjs → dns-record-BLeqo56C.mjs} +23 -3
  22. package/dist/dns-record-BLeqo56C.mjs.map +1 -0
  23. package/dist/effect-cloudflare.d.mts +2 -2
  24. package/dist/effect-cloudflare.mjs +2 -2
  25. package/dist/effect-vercel.d.mts +2 -2
  26. package/dist/effect-vercel.mjs +2 -2
  27. package/dist/effect.d.mts +9 -46
  28. package/dist/effect.mjs +29 -7
  29. package/dist/effect.mjs.map +1 -1
  30. package/dist/index-B1opsgls.d.mts +159 -0
  31. package/dist/{index-CEzk2gbZ.d.mts → index-Ewc__bQu.d.mts} +58 -7
  32. package/dist/index.d.mts +169 -113
  33. package/dist/index.mjs +201 -138
  34. package/dist/index.mjs.map +1 -1
  35. package/dist/{resolver-BHPsADzE.d.mts → resolver-CJOR9mQ8.d.mts} +620 -263
  36. package/dist/resolver-CxE-VDw4.mjs +598 -0
  37. package/dist/resolver-CxE-VDw4.mjs.map +1 -0
  38. package/dist/testing.d.mts +53 -44
  39. package/dist/testing.mjs +379 -107
  40. package/dist/testing.mjs.map +1 -1
  41. package/dist/{vercel-DD89DCqH.mjs → vercel-CWZ33NUS.mjs} +23 -3
  42. package/dist/vercel-CWZ33NUS.mjs.map +1 -0
  43. package/dist/vercel-DgJaUX9D.d.mts +31 -0
  44. package/dist/vercel.d.mts +3 -3
  45. package/dist/vercel.mjs +2 -2
  46. package/dist/verify-BzpiA0Es.mjs +642 -0
  47. package/dist/verify-BzpiA0Es.mjs.map +1 -0
  48. package/dist/verify-DZWLM91T.d.mts +805 -0
  49. package/dist/zone-discovery-BA5I24vu.d.mts +1390 -0
  50. package/dist/zones-DKHTpX9C.mjs +19 -0
  51. package/dist/zones-DKHTpX9C.mjs.map +1 -0
  52. package/package.json +1 -1
  53. package/dist/auth-Vi-hhXbV.mjs.map +0 -1
  54. package/dist/client-BnD7QLRF.mjs.map +0 -1
  55. package/dist/cloudflare-C6ryPlCZ.mjs.map +0 -1
  56. package/dist/cloudflare-tdjNS8vE.d.mts +0 -23
  57. package/dist/dns-record-C_KwGPLf.mjs.map +0 -1
  58. package/dist/index-CS9Rl5LQ.d.mts +0 -71
  59. package/dist/manifest-CP85ePFn.d.mts +0 -583
  60. package/dist/receipt-CGSnfZQ6.mjs +0 -28
  61. package/dist/receipt-CGSnfZQ6.mjs.map +0 -1
  62. package/dist/resolver-tEXpKxmH.mjs +0 -200
  63. package/dist/resolver-tEXpKxmH.mjs.map +0 -1
  64. package/dist/vercel-D-E2aqb1.d.mts +0 -22
  65. package/dist/vercel-DD89DCqH.mjs.map +0 -1
  66. package/dist/verify-B80xCBh2.d.mts +0 -1407
  67. package/dist/verify-BjrR_4NF.mjs +0 -1401
  68. package/dist/verify-BjrR_4NF.mjs.map +0 -1
package/dist/testing.mjs CHANGED
@@ -1,83 +1,166 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { C as toAsync$7, D as parse, b as Service } from "./dns-record-C_KwGPLf.mjs";
3
- import { _ as toAsync$10, a as toAsync$11, l as toAsync$12, m as Service$3, n as Service$4, o as Service$5, p as toAsync$9, u as Service$2, v as Service$1, x as toAsync$8 } from "./resolver-tEXpKxmH.mjs";
4
- import { r as toAsync$13, t as Service$6 } from "./receipt-CGSnfZQ6.mjs";
5
- import { Effect, Layer } from "effect";
6
- //#region src/stores/in-memory-connection.ts
7
- var in_memory_connection_exports = /* @__PURE__ */ __exportAll({
8
- layer: () => layer$6,
9
- make: () => make$6,
10
- toAsync: () => toAsync$6
2
+ import { M as Service, O as parse, _ as equals, b as Service$1, j as Error$1, v as parse$1, w as toAsync$2, x as fromAsync$1, y as Error$2 } from "./dns-record-BLeqo56C.mjs";
3
+ import { i as webCryptoLayer } from "./canonical-json-CpvqGMcr.mjs";
4
+ import { S as create, _ as PartialApplyError, b as apply, d as apply$1, f as authorize$1, h as ConflictError, i as toAsync$3, n as Service$2, p as create$1, v as StaleError, x as authorize, y as Target } from "./resolver-CxE-VDw4.mjs";
5
+ import { Effect, Layer, Ref, Schema, Semaphore } from "effect";
6
+ //#region src/stores/in-memory-lifecycle-repository.ts
7
+ var in_memory_lifecycle_repository_exports = /* @__PURE__ */ __exportAll({
8
+ layer: () => layer$2,
9
+ make: () => make$3
11
10
  });
12
- function make$6() {
13
- const connections = /* @__PURE__ */ new Map();
14
- return {
15
- delete: Effect.fn("InMemoryConnectionStore.delete")((connectionId) => Effect.sync(() => void connections.delete(connectionId))),
16
- find: Effect.fn("InMemoryConnectionStore.find")((ownerId, authorizationId) => Effect.sync(() => [...connections.values()].find((connection) => connection.ownerId === ownerId && connection.authorizationId === authorizationId) ?? null)),
17
- get: Effect.fn("InMemoryConnectionStore.get")((connectionId) => Effect.sync(() => connections.get(connectionId) ?? null)),
18
- listByAuthorizationId: Effect.fn("InMemoryConnectionStore.listByAuthorizationId")((authorizationId) => Effect.sync(() => [...connections.values()].filter((connection) => connection.authorizationId === authorizationId))),
19
- put: Effect.fn("InMemoryConnectionStore.put")((connection) => Effect.sync(() => void connections.set(connection.id, connection)))
20
- };
11
+ function missing(operation, message) {
12
+ return new Error$1({
13
+ category: "storage",
14
+ message,
15
+ operation,
16
+ retry: "never"
17
+ });
21
18
  }
22
- const layer$6 = () => Layer.succeed(Service$1, make$6());
23
- const toAsync$6 = (store = make$6()) => toAsync$8(store);
24
- //#endregion
25
- //#region src/stores/in-memory-authorization.ts
26
- var in_memory_authorization_exports = /* @__PURE__ */ __exportAll({
27
- layer: () => layer$5,
28
- make: () => make$5,
29
- toAsync: () => toAsync$5
30
- });
31
- const accountKey = (providerId, accountId) => `${providerId}\0${accountId}`;
32
- function make$5() {
33
- const authorizations = /* @__PURE__ */ new Map();
34
- const accounts = /* @__PURE__ */ new Map();
35
- return {
36
- delete: Effect.fn("InMemoryProviderAuthorizationStore.delete")((authorizationId) => Effect.sync(() => {
37
- const authorization = authorizations.get(authorizationId);
38
- if (authorization !== void 0) {
39
- accounts.delete(accountKey(authorization.providerId, authorization.accountId));
40
- authorizations.delete(authorizationId);
19
+ function make$3(options = {}) {
20
+ const state = Effect.runSync(Ref.make(/* @__PURE__ */ new Map()));
21
+ const mutations = Semaphore.makeUnsafe(1);
22
+ const serialize = (effect) => mutations.withPermit(effect);
23
+ const commit = Effect.fn("InMemoryAuthorizationLifecycleRepository.commit")(function* (operation, aggregate) {
24
+ if (options.beforeCommit !== void 0) yield* options.beforeCommit(operation, aggregate);
25
+ yield* Ref.update(state, (entries) => {
26
+ const next = new Map(entries);
27
+ if (aggregate === null) return next;
28
+ next.set(aggregate.authorization.id, aggregate);
29
+ return next;
30
+ });
31
+ });
32
+ const remove = Effect.fn("InMemoryAuthorizationLifecycleRepository.remove")(function* (authorizationId) {
33
+ if (options.beforeCommit !== void 0) yield* options.beforeCommit("remove", null);
34
+ yield* Ref.update(state, (entries) => {
35
+ const next = new Map(entries);
36
+ next.delete(authorizationId);
37
+ return next;
38
+ });
39
+ });
40
+ const get = Effect.fn("AuthorizationLifecycleRepository.get")((authorizationId) => Ref.get(state).pipe(Effect.map((entries) => entries.get(authorizationId) ?? null)));
41
+ const getByConnectionId = Effect.fn("AuthorizationLifecycleRepository.getByConnectionId")((connectionId) => Ref.get(state).pipe(Effect.map((entries) => [...entries.values()].find((aggregate) => aggregate.bindings.some((binding) => binding.id === connectionId)) ?? null)));
42
+ const finishRevocation = Effect.fn("AuthorizationLifecycleRepository.finishRevocation")(function* (aggregate, revoke) {
43
+ const pending = {
44
+ ...aggregate,
45
+ authorization: {
46
+ ...aggregate.authorization,
47
+ revocation: aggregate.authorization.revocation._tag === "Pending" ? aggregate.authorization.revocation : {
48
+ _tag: "Pending",
49
+ requestedAt: /* @__PURE__ */ new Date()
50
+ }
41
51
  }
42
- })),
43
- findByProviderAccount: Effect.fn("InMemoryProviderAuthorizationStore.findByProviderAccount")((providerId, accountId) => Effect.sync(() => {
44
- const id = accounts.get(accountKey(providerId, accountId));
45
- return id === void 0 ? null : authorizations.get(id) ?? null;
46
- })),
47
- get: Effect.fn("InMemoryProviderAuthorizationStore.get")((authorizationId) => Effect.sync(() => authorizations.get(authorizationId) ?? null)),
48
- put: Effect.fn("InMemoryProviderAuthorizationStore.put")((authorization) => Effect.sync(() => {
49
- authorizations.set(authorization.id, authorization);
50
- accounts.set(accountKey(authorization.providerId, authorization.accountId), authorization.id);
51
- }))
52
- };
52
+ };
53
+ yield* commit("prepareRevocation", pending);
54
+ yield* revoke(pending);
55
+ yield* remove(pending.authorization.id);
56
+ return {
57
+ authorizationId: pending.authorization.id,
58
+ remainingBindings: 0,
59
+ revokedAuthorization: true
60
+ };
61
+ });
62
+ return Service.of({
63
+ bind: Effect.fn("AuthorizationLifecycleRepository.bind")((authorizationId, binding) => serialize(Effect.gen(function* () {
64
+ const aggregate = yield* get(authorizationId);
65
+ if (aggregate === null) return yield* missing("bind", "Authorization aggregate does not exist");
66
+ const next = {
67
+ ...aggregate,
68
+ bindings: [...aggregate.bindings.filter((candidate) => candidate.id !== binding.id), binding]
69
+ };
70
+ yield* commit("bind", next);
71
+ return next;
72
+ }))),
73
+ connect: Effect.fn("AuthorizationLifecycleRepository.connect")((aggregate) => serialize(Effect.gen(function* () {
74
+ yield* commit("connect", aggregate);
75
+ return aggregate;
76
+ }))),
77
+ detach: ({ connectionId, revoke }) => serialize(Effect.gen(function* () {
78
+ const aggregate = yield* getByConnectionId(connectionId);
79
+ if (aggregate === null) return yield* missing("detach", "Connection binding does not exist");
80
+ const remaining = aggregate.bindings.filter((binding) => binding.id !== connectionId);
81
+ if (remaining.length > 0) {
82
+ yield* commit("detachBinding", {
83
+ ...aggregate,
84
+ bindings: remaining
85
+ });
86
+ return {
87
+ authorizationId: aggregate.authorization.id,
88
+ remainingBindings: remaining.length,
89
+ revokedAuthorization: false
90
+ };
91
+ }
92
+ return yield* finishRevocation(aggregate, revoke);
93
+ }).pipe(Effect.withSpan("AuthorizationLifecycleRepository.detach"))),
94
+ findByProviderAccount: Effect.fn("AuthorizationLifecycleRepository.findByProviderAccount")((providerId, providerAccountId) => Ref.get(state).pipe(Effect.map((entries) => [...entries.values()].find(({ authorization }) => authorization.providerId === providerId && authorization.providerAccountId === providerAccountId) ?? null))),
95
+ get,
96
+ getByConnectionId,
97
+ promoteEvidence: Effect.fn("AuthorizationLifecycleRepository.promoteEvidence")((authorizationId, evidence) => serialize(Effect.gen(function* () {
98
+ const aggregate = yield* get(authorizationId);
99
+ if (aggregate === null) return yield* missing("promoteEvidence", "Authorization aggregate does not exist");
100
+ const byCapability = new Map(aggregate.authorization.capabilityEvidence.map((item) => [item.capability, item]));
101
+ for (const item of evidence) byCapability.set(item.capability, item);
102
+ const next = {
103
+ ...aggregate,
104
+ authorization: {
105
+ ...aggregate.authorization,
106
+ capabilityEvidence: [...byCapability.values()]
107
+ }
108
+ };
109
+ yield* commit("promoteEvidence", next);
110
+ return next;
111
+ }))),
112
+ recover: ({ authorizationId, revoke }) => serialize(Effect.gen(function* () {
113
+ const aggregate = yield* get(authorizationId);
114
+ if (aggregate === null) return yield* missing("recover", "Authorization aggregate does not exist");
115
+ if (aggregate.authorization.revocation._tag !== "Pending") return yield* missing("recover", "Authorization is not awaiting revocation");
116
+ return yield* finishRevocation(aggregate, revoke);
117
+ }).pipe(Effect.withSpan("AuthorizationLifecycleRepository.recover"))),
118
+ rotate: Effect.fn("AuthorizationLifecycleRepository.rotate")((authorizationId, credential, expiresAt) => serialize(Effect.gen(function* () {
119
+ const aggregate = yield* get(authorizationId);
120
+ if (aggregate === null) return yield* missing("rotate", "Authorization aggregate does not exist");
121
+ const next = {
122
+ ...aggregate,
123
+ authorization: {
124
+ ...aggregate.authorization,
125
+ expiresAt
126
+ },
127
+ credential
128
+ };
129
+ yield* commit("rotate", next);
130
+ return next;
131
+ })))
132
+ });
53
133
  }
54
- const layer$5 = () => Layer.succeed(Service$2, make$5());
55
- const toAsync$5 = (store = make$5()) => toAsync$9(store);
134
+ const layer$2 = (options = {}) => Layer.sync(Service, () => make$3(options));
56
135
  //#endregion
57
- //#region src/stores/in-memory-credential.ts
58
- var in_memory_credential_exports = /* @__PURE__ */ __exportAll({
59
- layer: () => layer$4,
60
- make: () => make$4,
61
- toAsync: () => toAsync$4
62
- });
63
- function make$4() {
64
- const credentials = /* @__PURE__ */ new Map();
136
+ //#region src/stores/in-memory-continuation.ts
137
+ var in_memory_continuation_exports = /* @__PURE__ */ __exportAll({ make: () => make$2 });
138
+ function make$2() {
139
+ const state = Effect.runSync(Ref.make(/* @__PURE__ */ new Map()));
65
140
  return {
66
- delete: Effect.fn("InMemoryCredentialStore.delete")((authorizationId) => Effect.sync(() => void credentials.delete(authorizationId))),
67
- get: Effect.fn("InMemoryCredentialStore.get")((authorizationId) => Effect.sync(() => credentials.get(authorizationId) ?? null)),
68
- put: Effect.fn("InMemoryCredentialStore.put")((authorizationId, credential) => Effect.sync(() => void credentials.set(authorizationId, credential)))
141
+ consume: Effect.fn("ConnectionContinuations.consume")(function* (id, now) {
142
+ return yield* Ref.modify(state, (entries) => {
143
+ const continuation = entries.get(id);
144
+ const next = new Map(entries);
145
+ next.delete(id);
146
+ return [continuation !== void 0 && continuation.expiresAt > now ? continuation : null, next];
147
+ });
148
+ }),
149
+ put: Effect.fn("ConnectionContinuations.put")((continuation) => Ref.update(state, (entries) => {
150
+ const next = new Map(entries);
151
+ next.set(continuation.id, continuation);
152
+ return next;
153
+ }))
69
154
  };
70
155
  }
71
- const layer$4 = () => Layer.succeed(Service$3, make$4());
72
- const toAsync$4 = (store = make$4()) => toAsync$10(store);
73
156
  //#endregion
74
157
  //#region src/provider/in-memory.ts
75
158
  var in_memory_exports = /* @__PURE__ */ __exportAll({
76
- layer: () => layer$3,
77
- make: () => make$3,
78
- toAsync: () => toAsync$3
159
+ layer: () => layer$1,
160
+ make: () => make$1,
161
+ toAsync: () => toAsync$1
79
162
  });
80
- function make$3(options = {}) {
163
+ function make$1(options = {}) {
81
164
  const id = options.id ?? "memory";
82
165
  const records = /* @__PURE__ */ new Map();
83
166
  const recordIds = /* @__PURE__ */ new Map();
@@ -105,61 +188,250 @@ function make$3(options = {}) {
105
188
  getRecord: Effect.fn("InMemoryDnsProvider.getRecord")((_zone, providerRecordId) => Effect.sync(() => recordIds.get(providerRecordId) ?? null))
106
189
  };
107
190
  }
108
- const layer$3 = (options = {}) => Layer.succeed(Service, make$3(options));
109
- const toAsync$3 = (options = {}) => toAsync$7(make$3(options));
191
+ const layer$1 = (options = {}) => Layer.succeed(Service$1, make$1(options));
192
+ const toAsync$1 = (options = {}) => toAsync$2(make$1(options));
110
193
  //#endregion
111
194
  //#region src/verification/in-memory.ts
112
195
  var in_memory_exports$1 = /* @__PURE__ */ __exportAll({
113
- layer: () => layer$2,
114
- make: () => make$2,
115
- toAsync: () => toAsync$2
196
+ layer: () => layer,
197
+ make: () => make,
198
+ toAsync: () => toAsync
116
199
  });
117
- function make$2(resolve) {
200
+ function make(resolve) {
118
201
  return { resolve: Effect.fn("InMemoryDnsResolver.resolve")((query) => Effect.suspend(() => {
119
202
  const resolution = resolve(query);
120
203
  return Effect.isEffect(resolution) ? resolution : Effect.succeed(resolution);
121
204
  })) };
122
205
  }
123
- const layer$2 = (resolve) => Layer.succeed(Service$4, make$2(resolve));
124
- const toAsync$2 = (resolve) => toAsync$11(make$2(resolve));
206
+ const layer = (resolve) => Layer.succeed(Service$2, make(resolve));
207
+ const toAsync = (resolve) => toAsync$3(make(resolve));
125
208
  //#endregion
126
- //#region src/stores/in-memory-oauth-state.ts
127
- var in_memory_oauth_state_exports = /* @__PURE__ */ __exportAll({
128
- layer: () => layer$1,
129
- make: () => make$1,
130
- toAsync: () => toAsync$1
209
+ //#region src/testing/provider-conformance.ts
210
+ var provider_conformance_exports = /* @__PURE__ */ __exportAll({
211
+ Error: () => Error,
212
+ fromAsync: () => fromAsync,
213
+ run: () => run
131
214
  });
132
- function make$1() {
133
- const continuations = /* @__PURE__ */ new Map();
134
- return {
135
- put: Effect.fn("InMemoryOAuthStateStore.put")((continuation) => Effect.sync(() => void continuations.set(continuation.stateHash, continuation))),
136
- consume: Effect.fn("InMemoryOAuthStateStore.consume")((stateHash, now) => Effect.sync(() => {
137
- const continuation = continuations.get(stateHash) ?? null;
138
- if (continuation === null) return null;
139
- continuations.delete(stateHash);
140
- return continuation.expiresAt > now ? continuation : null;
141
- }))
142
- };
215
+ var Error = class extends Schema.TaggedError()("ProviderConformanceError", {
216
+ case: Schema.String,
217
+ message: Schema.String,
218
+ providerId: Schema.String
219
+ }) {};
220
+ /** Adapt a fresh Promise-shaped provider factory for the Effect-native conformance runner. */
221
+ function fromAsync(makeProvider) {
222
+ return () => Effect.sync(() => fromAsync$1(makeProvider()));
143
223
  }
144
- const layer$1 = () => Layer.succeed(Service$5, make$1());
145
- const toAsync$1 = (store = make$1()) => toAsync$12(store);
146
- //#endregion
147
- //#region src/stores/in-memory-receipt.ts
148
- var in_memory_receipt_exports = /* @__PURE__ */ __exportAll({
149
- layer: () => layer,
150
- make: () => make,
151
- toAsync: () => toAsync
152
- });
153
- function make() {
154
- const receipts = /* @__PURE__ */ new Map();
224
+ const cases = [
225
+ "create-readback-cleanup",
226
+ "exact-noop",
227
+ "conflict",
228
+ "stale-plan",
229
+ "partial-apply-cleanup"
230
+ ];
231
+ /** Run the deterministic offline contract every DomainKit DNS adapter must satisfy. */
232
+ const run = Effect.fn("ProviderConformance.run")(function* (input) {
233
+ const makeProvider = () => Effect.suspend(input.makeProvider).pipe(Effect.catchDefect((cause) => Effect.fail(new Error({
234
+ case: "factory",
235
+ message: cause instanceof globalThis.Error ? cause.message : "Provider factory failed",
236
+ providerId: "unknown"
237
+ }))));
238
+ const first = yield* makeProvider();
239
+ const providerId = first.id;
240
+ const prefix = input.prefix ?? "domainkit-conformance";
241
+ yield* createReadbackCleanup(first, input.zone, prefix);
242
+ yield* exactNoop(yield* makeProvider(), input.zone, prefix);
243
+ yield* conflict(yield* makeProvider(), input.zone, prefix);
244
+ yield* stalePlan(yield* makeProvider(), input.zone, prefix);
245
+ yield* partialApplyCleanup(yield* makeProvider(), input.zone, prefix);
155
246
  return {
156
- get: Effect.fn("InMemoryReceiptStore.get")((planDigest) => Effect.sync(() => receipts.get(planDigest) ?? null)),
157
- put: Effect.fn("InMemoryReceiptStore.put")((receipt) => Effect.sync(() => void receipts.set(receipt.planDigest, receipt)))
247
+ cases,
248
+ providerId,
249
+ zone: input.zone
158
250
  };
251
+ });
252
+ function createReadbackCleanup(provider, zone, prefix) {
253
+ const requirements = createRequirements(zone, prefix);
254
+ let cleanupRecordIds = [];
255
+ return Effect.gen(function* () {
256
+ const { plan } = yield* create({
257
+ requirements,
258
+ target: Target.ExactZone({ zone })
259
+ });
260
+ yield* requireTags(provider, "create-readback-cleanup", plan.operations, ["create", "create"]);
261
+ const receipt = yield* apply({
262
+ authorization: yield* authorize(plan),
263
+ plan
264
+ }).pipe(Effect.tapError((failure) => failure instanceof PartialApplyError ? Effect.sync(() => {
265
+ cleanupRecordIds = providerRecordIds(failure.receipt.operations);
266
+ }) : Effect.void));
267
+ cleanupRecordIds = providerRecordIds(receipt.operations);
268
+ yield* requireCondition(provider, "create-readback-cleanup", receipt.status === "complete" && receipt.operations.every(({ providerRecordId }) => providerRecordId !== null), "apply must return a complete receipt with provider record IDs");
269
+ const observed = yield* provider.listRecords(zone);
270
+ yield* requireCondition(provider, "create-readback-cleanup", requirements.every((requirement) => observed.some((record) => record._tag !== "Opaque" && equals(record, requirement))), "listRecords must return every created record, including across provider pages");
271
+ const deletion = yield* create$1({
272
+ plan,
273
+ receipt
274
+ });
275
+ yield* requireCondition(provider, "create-readback-cleanup", (yield* apply$1({
276
+ authorization: yield* authorize$1(deletion),
277
+ plan: deletion
278
+ })).status === "complete" && (yield* recordIdsAbsent(provider, zone, cleanupRecordIds)), "receipt-bound cleanup must remove every created record");
279
+ }).pipe(Effect.ensuring(bestEffortDelete(provider, zone, () => cleanupRecordIds)), Effect.provide(providerLayer(provider)));
280
+ }
281
+ function exactNoop(provider, zone, prefix) {
282
+ const requirement = cname(zone, `${prefix}-exact`);
283
+ let cleanupRecordIds = [];
284
+ return Effect.gen(function* () {
285
+ const created = yield* provider.createRecord(zone, requirement);
286
+ cleanupRecordIds = created.providerRecordId === null ? [] : [created.providerRecordId];
287
+ const { plan } = yield* create({
288
+ requirements: [requirement],
289
+ target: Target.ExactZone({ zone })
290
+ });
291
+ yield* requireTags(provider, "exact-noop", plan.operations, ["noop"]);
292
+ yield* requireCondition(provider, "exact-noop", (yield* apply({
293
+ authorization: yield* authorize(plan),
294
+ plan
295
+ })).operations.length === 0, "an exact plan must not create another record");
296
+ yield* deleteCreated(provider, zone, created.providerRecordId, "exact-noop");
297
+ }).pipe(Effect.ensuring(bestEffortDelete(provider, zone, () => cleanupRecordIds)), Effect.provide(providerLayer(provider)));
298
+ }
299
+ function conflict(provider, zone, prefix) {
300
+ const requirement = cname(zone, `${prefix}-conflict`);
301
+ const occupied = txt(zone, `${prefix}-conflict`, "occupied");
302
+ let cleanupRecordIds = [];
303
+ return Effect.gen(function* () {
304
+ const created = yield* provider.createRecord(zone, occupied);
305
+ cleanupRecordIds = created.providerRecordId === null ? [] : [created.providerRecordId];
306
+ const { plan } = yield* create({
307
+ requirements: [requirement],
308
+ target: Target.ExactZone({ zone })
309
+ });
310
+ yield* requireTags(provider, "conflict", plan.operations, ["conflict"]);
311
+ yield* requireCondition(provider, "conflict", (yield* apply({
312
+ authorization: yield* authorize(plan),
313
+ plan
314
+ }).pipe(Effect.flip)) instanceof ConflictError, "apply must fail closed on incompatible state");
315
+ yield* deleteCreated(provider, zone, created.providerRecordId, "conflict");
316
+ }).pipe(Effect.ensuring(bestEffortDelete(provider, zone, () => cleanupRecordIds)), Effect.provide(providerLayer(provider)));
317
+ }
318
+ function stalePlan(provider, zone, prefix) {
319
+ const requirement = cname(zone, `${prefix}-stale`);
320
+ let cleanupRecordIds = [];
321
+ return Effect.gen(function* () {
322
+ const { plan } = yield* create({
323
+ requirements: [requirement],
324
+ target: Target.ExactZone({ zone })
325
+ });
326
+ const authorization = yield* authorize(plan);
327
+ const created = yield* provider.createRecord(zone, requirement);
328
+ cleanupRecordIds = created.providerRecordId === null ? [] : [created.providerRecordId];
329
+ yield* requireCondition(provider, "stale-plan", (yield* apply({
330
+ authorization,
331
+ plan
332
+ }).pipe(Effect.flip)) instanceof StaleError, "apply must reject provider state that changed after authorization");
333
+ yield* deleteCreated(provider, zone, created.providerRecordId, "stale-plan");
334
+ }).pipe(Effect.ensuring(bestEffortDelete(provider, zone, () => cleanupRecordIds)), Effect.provide(providerLayer(provider)));
335
+ }
336
+ function partialApplyCleanup(provider, zone, prefix) {
337
+ let creates = 0;
338
+ const flaky = Service$1.of({
339
+ ...provider,
340
+ createRecord: Effect.fn("ProviderConformance.createRecord")((target, record) => {
341
+ creates += 1;
342
+ return creates === 1 ? provider.createRecord(target, record) : Effect.fail(new Error$2({
343
+ message: "injected conformance failure",
344
+ operation: "createRecord",
345
+ providerId: provider.id
346
+ }));
347
+ })
348
+ });
349
+ const requirements = [cname(zone, `${prefix}-partial-one`), txt(zone, `${prefix}-partial-two`, "domainkit")];
350
+ let cleanupRecordIds = [];
351
+ return Effect.gen(function* () {
352
+ const { plan } = yield* create({
353
+ requirements,
354
+ target: Target.ExactZone({ zone })
355
+ });
356
+ const failure = yield* apply({
357
+ authorization: yield* authorize(plan),
358
+ plan
359
+ }).pipe(Effect.flip);
360
+ cleanupRecordIds = failure instanceof PartialApplyError ? providerRecordIds(failure.receipt.operations) : [];
361
+ yield* requireCondition(provider, "partial-apply-cleanup", failure instanceof PartialApplyError && failure.receipt.operations.length === 1, "a later write failure must preserve the first confirmed write in a partial receipt");
362
+ if (!(failure instanceof PartialApplyError)) return;
363
+ const deletion = yield* create$1({
364
+ plan,
365
+ receipt: failure.receipt
366
+ }).pipe(Effect.provide(providerLayer(provider)));
367
+ yield* apply$1({
368
+ authorization: yield* authorize$1(deletion),
369
+ plan: deletion
370
+ }).pipe(Effect.provide(providerLayer(provider)));
371
+ yield* requireCondition(provider, "partial-apply-cleanup", yield* recordIdsAbsent(provider, zone, cleanupRecordIds), "partial receipt cleanup must remove the confirmed write");
372
+ }).pipe(Effect.ensuring(bestEffortDelete(provider, zone, () => cleanupRecordIds)), Effect.provide(providerLayer(flaky)));
373
+ }
374
+ function providerRecordIds(operations) {
375
+ return operations.flatMap(({ providerRecordId }) => providerRecordId === null ? [] : [providerRecordId]);
376
+ }
377
+ function recordIdsAbsent(provider, zone, recordIds) {
378
+ return Effect.forEach(recordIds, (providerRecordId) => provider.getRecord(zone, providerRecordId)).pipe(Effect.map((records) => records.every((record) => record === null)));
379
+ }
380
+ function bestEffortDelete(provider, zone, recordIds) {
381
+ return Effect.suspend(() => Effect.forEach(recordIds(), (providerRecordId) => provider.deleteRecord(zone, providerRecordId).pipe(Effect.ignore), { discard: true }));
382
+ }
383
+ function providerLayer(provider) {
384
+ return Layer.merge(Layer.succeed(Service$1, provider), webCryptoLayer);
385
+ }
386
+ function createRequirements(zone, prefix) {
387
+ return [cname(zone, `${prefix}-create`), txt(zone, `${prefix}-page`, "domainkit")];
388
+ }
389
+ function cname(zone, label) {
390
+ return parse$1({
391
+ _tag: "CNAME",
392
+ metadata: {
393
+ ownership: "tester",
394
+ provenance: "domainkit",
395
+ purpose: "conformance"
396
+ },
397
+ name: `${label}.${zone}`,
398
+ policy: "exclusive",
399
+ target: "target.example.net",
400
+ ttl: 300
401
+ });
402
+ }
403
+ function txt(zone, label, value) {
404
+ return parse$1({
405
+ _tag: "TXT",
406
+ metadata: {
407
+ ownership: "tester",
408
+ provenance: "domainkit",
409
+ purpose: "conformance"
410
+ },
411
+ name: `${label}.${zone}`,
412
+ policy: "append",
413
+ ttl: 300,
414
+ value
415
+ });
416
+ }
417
+ function requireTags(provider, testCase, operations, expected) {
418
+ return requireCondition(provider, testCase, JSON.stringify(operations.map(({ _tag }) => _tag)) === JSON.stringify(expected), `expected operations ${expected.join(", ")}`);
419
+ }
420
+ function requireCondition(provider, testCase, condition, message) {
421
+ return condition ? Effect.void : Effect.fail(new Error({
422
+ case: testCase,
423
+ message,
424
+ providerId: provider.id
425
+ }));
426
+ }
427
+ function deleteCreated(provider, zone, providerRecordId, testCase) {
428
+ return providerRecordId === null ? Effect.fail(new Error({
429
+ case: testCase,
430
+ message: "provider must return record IDs for safe cleanup",
431
+ providerId: provider.id
432
+ })) : provider.deleteRecord(zone, providerRecordId);
159
433
  }
160
- const layer = () => Layer.succeed(Service$6, make());
161
- const toAsync = (store = make()) => toAsync$13(store);
162
434
  //#endregion
163
- export { in_memory_connection_exports as InMemoryConnectionStore, in_memory_credential_exports as InMemoryCredentialStore, in_memory_exports as InMemoryDnsProvider, in_memory_exports$1 as InMemoryDnsResolver, in_memory_oauth_state_exports as InMemoryOAuthStateStore, in_memory_authorization_exports as InMemoryProviderAuthorizationStore, in_memory_receipt_exports as InMemoryReceiptStore };
435
+ export { in_memory_lifecycle_repository_exports as InMemoryAuthorizationLifecycle, in_memory_continuation_exports as InMemoryConnectionContinuations, in_memory_exports as InMemoryDnsProvider, in_memory_exports$1 as InMemoryDnsResolver, provider_conformance_exports as ProviderConformance };
164
436
 
165
437
  //# sourceMappingURL=testing.mjs.map