domainkit 0.14.1 → 0.16.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.
@@ -38,6 +38,11 @@ declare const Authorization_base: Schema.Class<Authorization, Schema.Struct<{
38
38
  readonly capabilities: Schema.$Array<Schema.Literals<readonly ["dns:read", "dns:write"]>>;
39
39
  /** Provider-specific account context (account id, team id, ...) decoded by the provider's `context` schema. */
40
40
  readonly context: Schema.Unknown;
41
+ /**
42
+ * What the provider called the account when the credential was issued, so a UI names the
43
+ * connection without a provider call. `null` when the provider names no single account.
44
+ */
45
+ readonly label: Schema.NullOr<Schema.String>;
41
46
  readonly revocation: Schema.Literals<readonly ["active", "pending", "revoked"]>;
42
47
  readonly createdBy: Schema.String;
43
48
  readonly createdAt: Schema.DateTimeUtcFromString;
@@ -125,6 +130,94 @@ declare const Attempt_base: Schema.Class<Attempt, Schema.Struct<{
125
130
  /** One durable plan -> approval -> receipt lifecycle. */
126
131
  export declare class Attempt extends Attempt_base {
127
132
  }
133
+ export declare const BatchId: Schema.brand<Schema.String, "@domainkit/BatchId">;
134
+ export type BatchId = typeof BatchId.Type;
135
+ /**
136
+ * Where a batch stands, recomputed from its items' attempts after every transition.
137
+ *
138
+ * `planning` while any item is still without a plan, `planned` once every item carries one,
139
+ * `approved` once the principal bound the batch digest, then the apply states. `partial` and
140
+ * `failed` are resumable; `complete` and `rejected` are terminal and leave the unfinished index.
141
+ */
142
+ export declare const BatchStatus: Schema.Literals<readonly ["planning", "planned", "approved", "applying", "complete", "partial", "failed", "rejected"]>;
143
+ export type BatchStatus = typeof BatchStatus.Type;
144
+ /** Who bound the batch to its digest, and when. The digest itself is `Batch.digest`. */
145
+ export declare const BatchApproval: Schema.Struct<{
146
+ readonly actorId: Schema.String;
147
+ readonly at: Schema.DateTimeUtcFromString;
148
+ }>;
149
+ export type BatchApproval = typeof BatchApproval.Type;
150
+ declare const Batch_base: Schema.Class<Batch, Schema.Struct<{
151
+ readonly id: Schema.brand<Schema.String, "@domainkit/BatchId">;
152
+ readonly ownerId: Schema.String;
153
+ readonly kind: Schema.Literals<readonly ["provisioning", "cleanup"]>;
154
+ readonly status: Schema.Literals<readonly ["planning", "planned", "approved", "applying", "complete", "partial", "failed", "rejected"]>;
155
+ /** SHA-256 over the items' `attachmentId:planDigest` pairs, bound at approval. */
156
+ readonly digest: Schema.NullOr<Schema.brand<Schema.String, "@domainkit/Digest">>;
157
+ readonly approval: Schema.NullOr<Schema.Struct<{
158
+ readonly actorId: Schema.String;
159
+ readonly at: Schema.DateTimeUtcFromString;
160
+ }>>;
161
+ readonly rejection: Schema.NullOr<Schema.Struct<{
162
+ readonly actorId: Schema.String;
163
+ readonly reason: Schema.NullOr<Schema.String>;
164
+ readonly at: Schema.DateTimeUtcFromString;
165
+ }>>;
166
+ /** Unique per owner: a replayed create returns the batch this key already made. */
167
+ readonly idempotencyKey: Schema.String;
168
+ readonly createdBy: Schema.String;
169
+ readonly createdAt: Schema.DateTimeUtcFromString;
170
+ readonly updatedAt: Schema.DateTimeUtcFromString;
171
+ /** When the batch reached `complete` or `rejected`; null while it still owes a move. */
172
+ readonly completedAt: Schema.NullOr<Schema.DateTimeUtcFromString>;
173
+ }>, {}>;
174
+ /**
175
+ * Many domains planned together, approved once, and applied with bounded concurrency.
176
+ *
177
+ * The batch owns the aggregate's own state only: its digest, who approved or declined it, and the
178
+ * status recomputed from its items. Every plan, approval, receipt, lease, and failure stays on the
179
+ * attempt an item points at, so nothing is stored twice.
180
+ */
181
+ export declare class Batch extends Batch_base {
182
+ }
183
+ declare const BatchItem_base: Schema.Class<BatchItem, Schema.Struct<{
184
+ readonly batchId: Schema.brand<Schema.String, "@domainkit/BatchId">;
185
+ readonly attachmentId: Schema.String;
186
+ /** The item's place in the batch, as `create` received it. */
187
+ readonly position: Schema.Number;
188
+ readonly attemptId: Schema.NullOr<Schema.brand<Schema.String, "@domainkit/PlanId">>;
189
+ readonly planFailure: Schema.NullOr<Schema.String>;
190
+ }>, {}>;
191
+ /**
192
+ * One domain's place in a batch: a pointer to the attachment and to the attempt carrying its plan.
193
+ *
194
+ * `attemptId` is null until the item is planned, and `planFailure` carries why the last planning
195
+ * pass stopped, cleared the moment a plan lands.
196
+ */
197
+ export declare class BatchItem extends BatchItem_base {
198
+ }
199
+ /**
200
+ * The status a batch holds, given whether it was approved or declined and where each item's
201
+ * attempt stands. `null` names an item that has no plan yet.
202
+ *
203
+ * Both `Storage` implementations recompute the stored status through this after every transition,
204
+ * so the owner-scoped unfinished index never disagrees with the attempts it summarizes.
205
+ */
206
+ export declare const batchStatusOf: (input: {
207
+ readonly approved: boolean;
208
+ readonly rejected: boolean;
209
+ readonly items: ReadonlyArray<AttemptStatus | null>;
210
+ }) => BatchStatus;
211
+ /** A batch with its items, in `position` order. Every batch read returns one. */
212
+ export interface BatchAggregate {
213
+ readonly batch: Batch;
214
+ readonly items: ReadonlyArray<BatchItem>;
215
+ }
216
+ /** One item's attempt approval, as `batches.approve` writes it beside the batch's own. */
217
+ export interface BatchItemApproval {
218
+ readonly attachmentId: string;
219
+ readonly approval: Approval.Model;
220
+ }
128
221
  export declare const RequirementStatus: Schema.Literals<readonly ["satisfied", "missing", "mismatch", "unknown"]>;
129
222
  export type RequirementStatus = typeof RequirementStatus.Type;
130
223
  export declare const Overall: Schema.Literals<readonly ["ready", "pending", "failed"]>;
@@ -242,6 +335,72 @@ export interface Interface {
242
335
  readonly complete: (id: Plan.PlanId, receipt: Receipt.Model) => Fx<Attempt>;
243
336
  readonly fail: (id: Plan.PlanId, message: string) => Fx<Attempt>;
244
337
  };
338
+ /**
339
+ * The batch aggregate: many attempts planned together and approved once.
340
+ *
341
+ * Every transition runs in one transaction over the locked batch row and leaves `status`
342
+ * recomputed from the items' attempts, so the owner-scoped unfinished index never disagrees
343
+ * with the attempts it summarizes.
344
+ */
345
+ readonly batches: {
346
+ /**
347
+ * Insert the batch and one item per attachment, or return the batch this owner already
348
+ * created under `idempotencyKey`. A replay ignores `attachmentIds` and returns what is
349
+ * stored, which is what makes a retried create safe.
350
+ */
351
+ readonly create: (input: {
352
+ readonly kind: Plan.Kind;
353
+ readonly idempotencyKey: string;
354
+ /** One item per attachment, in order; empty or duplicated fails `InvalidInput`. */
355
+ readonly attachmentIds: ReadonlyArray<string>;
356
+ }) => Fx<BatchAggregate>;
357
+ readonly get: (id: BatchId) => Fx<BatchAggregate>;
358
+ /**
359
+ * The batch this owner created under `idempotencyKey`, if any.
360
+ *
361
+ * What a caller reads before it validates a create's payload, so a retried request answers
362
+ * with the batch the first one made whatever the retry now says.
363
+ */
364
+ readonly byIdempotencyKey: (key: string) => Fx<Option.Option<BatchAggregate>>;
365
+ /** Every batch that has not reached `complete` or `rejected`, most recently touched first. */
366
+ readonly listUnfinished: () => Fx<ReadonlyArray<BatchAggregate>>;
367
+ /**
368
+ * Point an item at the attempt that now carries its plan and clear its plan failure.
369
+ *
370
+ * Fails `Stale` unless the batch is still `planning` or `planned`. That check is the fence a
371
+ * planner still in flight hits when the principal declines the batch underneath it, so plan
372
+ * state never lands on a rejected aggregate.
373
+ */
374
+ readonly recordItemPlan: (id: BatchId, attachmentId: string, attemptId: Plan.PlanId) => Fx<BatchAggregate>;
375
+ /** Record why an item could not be planned; the batch stays `planning`. Same fence. */
376
+ readonly recordItemPlanFailure: (id: BatchId, attachmentId: string, message: string) => Fx<BatchAggregate>;
377
+ /**
378
+ * Bind the batch to `digest` and approve every item's attempt in the same transaction, so a
379
+ * batch is never approved without the per-attempt approvals `attempts.apply` needs.
380
+ *
381
+ * Fails `Stale` unless the batch is `planned` and every item still points at the attempt its
382
+ * approval names. Approving an approved batch under the same digest returns it unchanged; a
383
+ * different digest fails `Stale`.
384
+ */
385
+ readonly approve: (id: BatchId, input: {
386
+ readonly digest: Plan.Digest;
387
+ readonly actorId: string;
388
+ /** One per item, covering every item in the batch. */
389
+ readonly approvals: ReadonlyArray<BatchItemApproval>;
390
+ }) => Fx<BatchAggregate>;
391
+ /**
392
+ * Decline the batch and every planned attempt under it, in one transaction. Terminal.
393
+ *
394
+ * Rejecting again returns the batch unchanged; a batch that reached `approved` or later
395
+ * fails `Stale`.
396
+ */
397
+ readonly reject: (id: BatchId, input: {
398
+ readonly actorId: string;
399
+ readonly reason: string | null;
400
+ }) => Fx<BatchAggregate>;
401
+ /** Recompute the stored status from the items' attempts, after applying moved them. */
402
+ readonly refresh: (id: BatchId) => Fx<BatchAggregate>;
403
+ };
245
404
  readonly readiness: {
246
405
  /** One row per (owner, domain); `attachmentId`, when set, must exist for the owner. */
247
406
  readonly put: (readiness: Readiness) => Fx<void>;
@@ -329,6 +488,28 @@ export interface AsyncInterface {
329
488
  readonly complete: (principal: Principal.Interface, id: Plan.PlanId, receipt: Receipt.Model) => Promise<Attempt>;
330
489
  readonly fail: (principal: Principal.Interface, id: Plan.PlanId, message: string) => Promise<Attempt>;
331
490
  };
491
+ readonly batches: {
492
+ readonly create: (principal: Principal.Interface, input: {
493
+ readonly kind: Plan.Kind;
494
+ readonly idempotencyKey: string;
495
+ readonly attachmentIds: ReadonlyArray<string>;
496
+ }) => Promise<BatchAggregate>;
497
+ readonly get: (principal: Principal.Interface, id: BatchId) => Promise<BatchAggregate>;
498
+ readonly byIdempotencyKey: (principal: Principal.Interface, key: string) => Promise<BatchAggregate | null>;
499
+ readonly listUnfinished: (principal: Principal.Interface) => Promise<ReadonlyArray<BatchAggregate>>;
500
+ readonly recordItemPlan: (principal: Principal.Interface, id: BatchId, attachmentId: string, attemptId: Plan.PlanId) => Promise<BatchAggregate>;
501
+ readonly recordItemPlanFailure: (principal: Principal.Interface, id: BatchId, attachmentId: string, message: string) => Promise<BatchAggregate>;
502
+ readonly approve: (principal: Principal.Interface, id: BatchId, input: {
503
+ readonly digest: Plan.Digest;
504
+ readonly actorId: string;
505
+ readonly approvals: ReadonlyArray<BatchItemApproval>;
506
+ }) => Promise<BatchAggregate>;
507
+ readonly reject: (principal: Principal.Interface, id: BatchId, input: {
508
+ readonly actorId: string;
509
+ readonly reason: string | null;
510
+ }) => Promise<BatchAggregate>;
511
+ readonly refresh: (principal: Principal.Interface, id: BatchId) => Promise<BatchAggregate>;
512
+ };
332
513
  readonly readiness: {
333
514
  readonly put: (principal: Principal.Interface, readiness: Readiness) => Promise<void>;
334
515
  readonly get: (principal: Principal.Interface, domain: string) => Promise<Readiness | null>;
@@ -22,6 +22,8 @@ export interface FakeProviderOptions {
22
22
  readonly zones?: ReadonlyArray<string>;
23
23
  /** The label each zone carries, keyed by zone. Default: the zone name. */
24
24
  readonly labels?: Readonly<Record<string, string>>;
25
+ /** What the fake calls the account it issues credentials for. Default: none, so the label is `null`. */
26
+ readonly accountLabel?: string;
25
27
  /** Pre-existing records, to produce `Noop` and `Conflict` operations. */
26
28
  readonly records?: ReadonlyArray<{
27
29
  readonly zone: string;
@@ -29,6 +29,7 @@ export declare const make: (storage: Storage.Interface, connect: Connect.Interfa
29
29
  readonly sourceReceiptId: Receipt.ReceiptId | null;
30
30
  }) => Fx<Plan.Model>;
31
31
  approve: (plan: Plan.Model | Plan.PlanId, options?: ApproveOptions) => Fx<Approval.Model>;
32
+ draftApproval: (attempt: Storage.Attempt, options?: ApproveOptions) => Fx<Approval.Model>;
32
33
  reject: (plan: Plan.Model | Plan.PlanId, options?: {
33
34
  readonly reason?: string;
34
35
  }) => Fx<Storage.Attempt>;
@@ -7,7 +7,7 @@ import { Effect, Schema } from "effect";
7
7
  import * as Reason from "../Reason.ts";
8
8
  export type Category = "request" | "auth" | "plan" | "provider" | "storage" | "internal";
9
9
  declare const DomainKitError_base: Schema.Class<DomainKitError, Schema.TaggedStruct<"DomainKitError", {
10
- readonly reason: Schema.suspend<Schema.Union<readonly [typeof Reason.InvalidInput, typeof Reason.Unauthenticated, typeof Reason.Forbidden, typeof Reason.NotFound, typeof Reason.Conflict, typeof Reason.Stale, typeof Reason.Expired, typeof Reason.Busy, typeof Reason.ProviderRejected, typeof Reason.ProviderUnavailable, typeof Reason.ProviderConflict, typeof Reason.Unsupported, typeof Reason.Reconnect, typeof Reason.StorageFailed, typeof Reason.CryptoFailed, typeof Reason.ResolverFailed]>>;
10
+ readonly reason: Schema.suspend<Schema.Union<readonly [typeof Reason.InvalidInput, typeof Reason.Unauthenticated, typeof Reason.Forbidden, typeof Reason.NotFound, typeof Reason.Conflict, typeof Reason.Stale, typeof Reason.BatchStale, typeof Reason.Expired, typeof Reason.Busy, typeof Reason.ProviderRejected, typeof Reason.ProviderUnavailable, typeof Reason.ProviderConflict, typeof Reason.Unsupported, typeof Reason.Reconnect, typeof Reason.StorageFailed, typeof Reason.CryptoFailed, typeof Reason.ResolverFailed]>>;
11
11
  }>, import("effect/Cause").YieldableError>;
12
12
  export declare class DomainKitError extends DomainKitError_base {
13
13
  get category(): Category;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domainkit",
3
- "version": "0.14.1",
3
+ "version": "0.16.0",
4
4
  "description": "Build reviewable DNS plans for custom domains in TypeScript",
5
5
  "keywords": [
6
6
  "custom-domains",
@@ -157,5 +157,5 @@
157
157
  "engines": {
158
158
  "node": ">=24.10.0"
159
159
  },
160
- "packageManager": "bun@1.4.0"
160
+ "packageManager": "bun@1.4.2"
161
161
  }