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.
- package/README.md +9 -2
- package/dist/Cloudflare.mjs +16 -7
- package/dist/Cloudflare.mjs.map +1 -1
- package/dist/Connect.mjs +5 -1
- package/dist/Connect.mjs.map +1 -1
- package/dist/Provider.mjs.map +1 -1
- package/dist/Provision.mjs +209 -19
- package/dist/Provision.mjs.map +1 -1
- package/dist/Reason.mjs +22 -2
- package/dist/Reason.mjs.map +1 -1
- package/dist/Server.mjs +89 -5
- package/dist/Server.mjs.map +1 -1
- package/dist/Storage.mjs +105 -2
- package/dist/Storage.mjs.map +1 -1
- package/dist/Testing.mjs +2 -0
- package/dist/Testing.mjs.map +1 -1
- package/dist/internal/attempts.mjs +14 -4
- package/dist/internal/attempts.mjs.map +1 -1
- package/dist/internal/conformance/provider.mjs +1 -0
- package/dist/internal/conformance/provider.mjs.map +1 -1
- package/dist/internal/conformance/storage.mjs +185 -0
- package/dist/internal/conformance/storage.mjs.map +1 -1
- package/dist/internal/error.mjs +2 -0
- package/dist/internal/error.mjs.map +1 -1
- package/dist/internal/storage-memory.mjs +185 -2
- package/dist/internal/storage-memory.mjs.map +1 -1
- package/dist/package.mjs +1 -1
- package/dist/types/Connect.d.ts +3 -1
- package/dist/types/Provider.d.ts +6 -0
- package/dist/types/Provision.d.ts +124 -3
- package/dist/types/Reason.d.ts +18 -2
- package/dist/types/Server.d.ts +3623 -202
- package/dist/types/Storage.d.ts +181 -0
- package/dist/types/Testing.d.ts +2 -0
- package/dist/types/internal/attempts.d.ts +1 -0
- package/dist/types/internal/error.d.ts +1 -1
- package/package.json +2 -2
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { PlanId } from "../Plan.mjs";
|
|
2
|
-
import { Busy, Expired, InvalidInput, NotFound, Stale } from "../Reason.mjs";
|
|
2
|
+
import { BatchStale, Busy, Expired, InvalidInput, NotFound, Stale } from "../Reason.mjs";
|
|
3
3
|
import { fail } from "./error.mjs";
|
|
4
4
|
import { fresh } from "./ids.mjs";
|
|
5
5
|
import { Service } from "../Principal.mjs";
|
|
6
|
-
import { Attachment, Attempt, Authorization, Connection, ContinuationHeader } from "../Storage.mjs";
|
|
6
|
+
import { Attachment, Attempt, Authorization, Batch, BatchId, BatchItem, Connection, ContinuationHeader, batchStatusOf } from "../Storage.mjs";
|
|
7
7
|
import { DateTime, Effect, Option, Semaphore } from "effect";
|
|
8
8
|
//#region src/internal/storage-memory.ts
|
|
9
9
|
const notFound = (entity, id) => fail(new NotFound({
|
|
@@ -22,6 +22,8 @@ function makeMemory(options = {}) {
|
|
|
22
22
|
attachments: /* @__PURE__ */ new Map(),
|
|
23
23
|
continuations: /* @__PURE__ */ new Map(),
|
|
24
24
|
attempts: /* @__PURE__ */ new Map(),
|
|
25
|
+
batches: /* @__PURE__ */ new Map(),
|
|
26
|
+
batchItems: /* @__PURE__ */ new Map(),
|
|
25
27
|
readiness: /* @__PURE__ */ new Map(),
|
|
26
28
|
locks: /* @__PURE__ */ new Set()
|
|
27
29
|
};
|
|
@@ -54,6 +56,52 @@ function makeMemory(options = {}) {
|
|
|
54
56
|
const row = ownedRows(state.attempts, principal).find(predicate);
|
|
55
57
|
return row === void 0 ? notFound(entity, id) : Effect.succeed(row);
|
|
56
58
|
});
|
|
59
|
+
const batch = (principal, id) => Effect.suspend(() => {
|
|
60
|
+
const row = owned(state.batches, principal, id);
|
|
61
|
+
return row === void 0 ? notFound("batch", id) : Effect.succeed(row);
|
|
62
|
+
});
|
|
63
|
+
const itemsOf = (batchId) => [...state.batchItems.get(batchId) ?? []].sort((left, right) => left.position - right.position);
|
|
64
|
+
const aggregateOf = (row) => ({
|
|
65
|
+
batch: row,
|
|
66
|
+
items: itemsOf(row.id)
|
|
67
|
+
});
|
|
68
|
+
const batchStale = (row) => fail(new BatchStale({
|
|
69
|
+
batchId: row.id,
|
|
70
|
+
status: row.status,
|
|
71
|
+
digest: row.digest
|
|
72
|
+
}));
|
|
73
|
+
const statusOf = (row, items) => batchStatusOf({
|
|
74
|
+
approved: row.approval !== null,
|
|
75
|
+
rejected: row.rejection !== null,
|
|
76
|
+
items: items.map((item) => item.attemptId === null ? null : state.attempts.get(item.attemptId)?.status ?? null)
|
|
77
|
+
});
|
|
78
|
+
/** Store `row` with its status recomputed from the items' attempts, and return the aggregate. */
|
|
79
|
+
const settle = (row, now) => {
|
|
80
|
+
const items = itemsOf(row.id);
|
|
81
|
+
const status = statusOf(row, items);
|
|
82
|
+
const next = new Batch({
|
|
83
|
+
...row,
|
|
84
|
+
status,
|
|
85
|
+
completedAt: status === "complete" || status === "rejected" ? now : null,
|
|
86
|
+
updatedAt: now
|
|
87
|
+
});
|
|
88
|
+
state.batches.set(row.id, next);
|
|
89
|
+
return {
|
|
90
|
+
batch: next,
|
|
91
|
+
items
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
const replaceItem = (item) => {
|
|
95
|
+
const items = state.batchItems.get(item.batchId) ?? [];
|
|
96
|
+
const index = items.findIndex((row) => row.attachmentId === item.attachmentId);
|
|
97
|
+
if (index >= 0) items[index] = item;
|
|
98
|
+
};
|
|
99
|
+
/** Only a batch still being planned accepts plan state, an approval, or a rejection. */
|
|
100
|
+
const openForPlanning = (row) => row.status === "planning" || row.status === "planned" ? Effect.void : batchStale(row);
|
|
101
|
+
const itemAt = (row, attachmentId) => Effect.suspend(() => {
|
|
102
|
+
const item = itemsOf(row.id).find((entry) => entry.attachmentId === attachmentId);
|
|
103
|
+
return item === void 0 ? notFound("attachment", attachmentId) : Effect.succeed(item);
|
|
104
|
+
});
|
|
57
105
|
const finishRevocation = (principal, row, revoke) => Effect.gen(function* () {
|
|
58
106
|
yield* commit("authorizations.prepareRevocation");
|
|
59
107
|
state.authorizations.set(row.id, new Authorization({
|
|
@@ -289,6 +337,141 @@ function makeMemory(options = {}) {
|
|
|
289
337
|
return next;
|
|
290
338
|
}))
|
|
291
339
|
},
|
|
340
|
+
batches: {
|
|
341
|
+
create: (input) => write((principal) => Effect.gen(function* () {
|
|
342
|
+
const replay = ownedRows(state.batches, principal).find((row) => row.idempotencyKey === input.idempotencyKey);
|
|
343
|
+
if (replay !== void 0) return aggregateOf(replay);
|
|
344
|
+
if (input.attachmentIds.length === 0) return yield* invalid("A batch needs at least one attachment", "attachmentIds");
|
|
345
|
+
if (new Set(input.attachmentIds).size !== input.attachmentIds.length) return yield* invalid("A batch holds one item per attachment", "attachmentIds");
|
|
346
|
+
for (const attachmentId of input.attachmentIds) yield* attachment(principal, attachmentId);
|
|
347
|
+
const now = yield* DateTime.now;
|
|
348
|
+
const id = BatchId.make(yield* fresh("batch"));
|
|
349
|
+
const row = new Batch({
|
|
350
|
+
id,
|
|
351
|
+
ownerId: principal.ownerId,
|
|
352
|
+
kind: input.kind,
|
|
353
|
+
status: "planning",
|
|
354
|
+
digest: null,
|
|
355
|
+
approval: null,
|
|
356
|
+
rejection: null,
|
|
357
|
+
idempotencyKey: input.idempotencyKey,
|
|
358
|
+
createdBy: principal.actorId,
|
|
359
|
+
createdAt: now,
|
|
360
|
+
updatedAt: now,
|
|
361
|
+
completedAt: null
|
|
362
|
+
});
|
|
363
|
+
yield* commit("batches.create");
|
|
364
|
+
state.batches.set(id, row);
|
|
365
|
+
state.batchItems.set(id, input.attachmentIds.map((attachmentId, position) => new BatchItem({
|
|
366
|
+
batchId: id,
|
|
367
|
+
attachmentId,
|
|
368
|
+
position,
|
|
369
|
+
attemptId: null,
|
|
370
|
+
planFailure: null
|
|
371
|
+
})));
|
|
372
|
+
return aggregateOf(row);
|
|
373
|
+
})),
|
|
374
|
+
get: (id) => read((principal) => Effect.map(batch(principal, id), aggregateOf)),
|
|
375
|
+
byIdempotencyKey: (key) => read((principal) => Effect.sync(() => Option.map(Option.fromNullishOr(ownedRows(state.batches, principal).find((row) => row.idempotencyKey === key)), aggregateOf))),
|
|
376
|
+
listUnfinished: () => read((principal) => Effect.sync(() => ownedRows(state.batches, principal).filter((row) => row.status !== "complete" && row.status !== "rejected").sort((left, right) => DateTime.toEpochMillis(right.updatedAt) - DateTime.toEpochMillis(left.updatedAt)).map(aggregateOf))),
|
|
377
|
+
recordItemPlan: (id, attachmentId, attemptId) => write((principal) => Effect.gen(function* () {
|
|
378
|
+
const row = yield* batch(principal, id);
|
|
379
|
+
yield* openForPlanning(row);
|
|
380
|
+
const item = yield* itemAt(row, attachmentId);
|
|
381
|
+
const planned = yield* attempt(principal, attemptId);
|
|
382
|
+
if (planned.attachmentId !== attachmentId || planned.kind !== row.kind) return yield* invalid(`Plan ${attemptId} does not belong to attachment ${attachmentId} in this batch`, "attemptId");
|
|
383
|
+
yield* commit("batches.recordItemPlan");
|
|
384
|
+
replaceItem(new BatchItem({
|
|
385
|
+
...item,
|
|
386
|
+
attemptId,
|
|
387
|
+
planFailure: null
|
|
388
|
+
}));
|
|
389
|
+
return settle(row, yield* DateTime.now);
|
|
390
|
+
})),
|
|
391
|
+
recordItemPlanFailure: (id, attachmentId, message) => write((principal) => Effect.gen(function* () {
|
|
392
|
+
const row = yield* batch(principal, id);
|
|
393
|
+
yield* openForPlanning(row);
|
|
394
|
+
const item = yield* itemAt(row, attachmentId);
|
|
395
|
+
yield* commit("batches.recordItemPlanFailure");
|
|
396
|
+
replaceItem(new BatchItem({
|
|
397
|
+
...item,
|
|
398
|
+
attemptId: null,
|
|
399
|
+
planFailure: message
|
|
400
|
+
}));
|
|
401
|
+
return settle(row, yield* DateTime.now);
|
|
402
|
+
})),
|
|
403
|
+
approve: (id, input) => write((principal) => Effect.gen(function* () {
|
|
404
|
+
const row = yield* batch(principal, id);
|
|
405
|
+
if (row.approval !== null) return row.digest === input.digest ? aggregateOf(row) : yield* batchStale(row);
|
|
406
|
+
if (row.status !== "planned") return yield* batchStale(row);
|
|
407
|
+
const items = itemsOf(id);
|
|
408
|
+
const pairs = [];
|
|
409
|
+
for (const item of items) {
|
|
410
|
+
const supplied = input.approvals.find((entry) => entry.attachmentId === item.attachmentId);
|
|
411
|
+
if (supplied === void 0) return yield* invalid(`Batch ${id} has no approval for attachment ${item.attachmentId}`, "approvals");
|
|
412
|
+
const stored = item.attemptId === null ? void 0 : state.attempts.get(item.attemptId);
|
|
413
|
+
if (stored === void 0 || stored.id !== supplied.approval.planId) return yield* batchStale(row);
|
|
414
|
+
if (stored.approval?.id !== supplied.approval.id && stored.status !== "planned") return yield* stale(stored);
|
|
415
|
+
pairs.push({
|
|
416
|
+
attempt: stored,
|
|
417
|
+
approval: supplied.approval
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
const now = yield* DateTime.now;
|
|
421
|
+
yield* commit("batches.approve");
|
|
422
|
+
for (const { attempt: stored, approval } of pairs) {
|
|
423
|
+
if (stored.approval?.id === approval.id) continue;
|
|
424
|
+
state.attempts.set(stored.id, new Attempt({
|
|
425
|
+
...stored,
|
|
426
|
+
status: "approved",
|
|
427
|
+
approval,
|
|
428
|
+
updatedAt: now
|
|
429
|
+
}));
|
|
430
|
+
}
|
|
431
|
+
return settle(new Batch({
|
|
432
|
+
...row,
|
|
433
|
+
digest: input.digest,
|
|
434
|
+
approval: {
|
|
435
|
+
actorId: input.actorId,
|
|
436
|
+
at: now
|
|
437
|
+
}
|
|
438
|
+
}), now);
|
|
439
|
+
})),
|
|
440
|
+
reject: (id, input) => write((principal) => Effect.gen(function* () {
|
|
441
|
+
const row = yield* batch(principal, id);
|
|
442
|
+
if (row.rejection !== null) return aggregateOf(row);
|
|
443
|
+
yield* openForPlanning(row);
|
|
444
|
+
const now = yield* DateTime.now;
|
|
445
|
+
yield* commit("batches.reject");
|
|
446
|
+
for (const item of itemsOf(id)) {
|
|
447
|
+
const stored = item.attemptId === null ? void 0 : state.attempts.get(item.attemptId);
|
|
448
|
+
if (stored === void 0 || stored.status !== "planned") continue;
|
|
449
|
+
state.attempts.set(stored.id, new Attempt({
|
|
450
|
+
...stored,
|
|
451
|
+
status: "rejected",
|
|
452
|
+
rejection: {
|
|
453
|
+
actorId: input.actorId,
|
|
454
|
+
reason: input.reason,
|
|
455
|
+
at: now
|
|
456
|
+
},
|
|
457
|
+
updatedAt: now
|
|
458
|
+
}));
|
|
459
|
+
}
|
|
460
|
+
return settle(new Batch({
|
|
461
|
+
...row,
|
|
462
|
+
rejection: {
|
|
463
|
+
actorId: input.actorId,
|
|
464
|
+
reason: input.reason,
|
|
465
|
+
at: now
|
|
466
|
+
}
|
|
467
|
+
}), now);
|
|
468
|
+
})),
|
|
469
|
+
refresh: (id) => write((principal) => Effect.gen(function* () {
|
|
470
|
+
const row = yield* batch(principal, id);
|
|
471
|
+
yield* commit("batches.refresh");
|
|
472
|
+
return settle(row, yield* DateTime.now);
|
|
473
|
+
}))
|
|
474
|
+
},
|
|
292
475
|
readiness: {
|
|
293
476
|
put: (readiness) => write((principal) => Effect.gen(function* () {
|
|
294
477
|
if (readiness.ownerId !== principal.ownerId) return yield* invalid("Readiness owner does not match the principal", "ownerId");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage-memory.mjs","names":["Errors.fail","Reason.NotFound","Reason.InvalidInput","Principal.Service","Storage.Authorization","Reason.Busy","Storage.Connection","Storage.Attachment","Reason.Expired","Storage.ContinuationHeader","Storage.Attempt","Reason.Stale"],"sources":["../../src/internal/storage-memory.ts"],"sourcesContent":["import { DateTime, Effect, Option, Semaphore } from \"effect\";\n\nimport type * as Approval from \"../Approval.ts\";\nimport * as Errors from \"./error.ts\";\nimport * as Reason from \"../Reason.ts\";\nimport * as Plan from \"../Plan.ts\";\nimport * as Principal from \"../Principal.ts\";\nimport type * as Receipt from \"../Receipt.ts\";\nimport * as Storage from \"../Storage.ts\";\nimport { fresh } from \"./ids.ts\";\n\ninterface State {\n readonly authorizations: Map<string, Storage.Authorization>;\n readonly credentials: Map<string, Storage.Credential>;\n readonly connections: Map<string, Storage.Connection>;\n readonly attachments: Map<string, Storage.Attachment>;\n readonly continuations: Map<string, Storage.Continuation>;\n readonly attempts: Map<string, Storage.Attempt>;\n readonly readiness: Map<string, Storage.Readiness>;\n readonly locks: Set<string>;\n}\n\nconst notFound = (entity: Reason.NotFound[\"entity\"], id: string) =>\n Errors.fail(new Reason.NotFound({ entity, id }));\nconst invalid = (message: string, field?: string) =>\n Errors.fail(new Reason.InvalidInput({ message, ...(field === undefined ? {} : { field }) }));\n\nexport function makeMemory(options: Storage.MemoryOptions = {}): Storage.Interface {\n const state: State = {\n authorizations: new Map(),\n credentials: new Map(),\n connections: new Map(),\n attachments: new Map(),\n continuations: new Map(),\n attempts: new Map(),\n readiness: new Map(),\n locks: new Set(),\n };\n const mutations = Semaphore.makeUnsafe(1);\n const commit = (operation: string) =>\n options.beforeCommit === undefined ? Effect.void : options.beforeCommit(operation);\n\n const read = <A, E, R = never>(\n run: (principal: Principal.Interface) => Effect.Effect<A, E, R>,\n ): Effect.Effect<A, E, Principal.Service | R> => Effect.flatMap(Principal.Service, run);\n const write = <A, E>(\n run: (principal: Principal.Interface) => Effect.Effect<A, E>,\n ): Effect.Effect<A, E, Principal.Service> =>\n Effect.flatMap(Principal.Service, (principal) => mutations.withPermit(run(principal)));\n\n const owned = <Row extends { readonly ownerId: string }>(\n rows: Map<string, Row>,\n principal: Principal.Interface,\n id: string,\n ): Row | undefined => {\n const row = rows.get(id);\n return row !== undefined && row.ownerId === principal.ownerId ? row : undefined;\n };\n const ownedRows = <Row extends { readonly ownerId: string }>(\n rows: Map<string, Row>,\n principal: Principal.Interface,\n ) => [...rows.values()].filter((row) => row.ownerId === principal.ownerId);\n\n const authorization = (principal: Principal.Interface, id: string) =>\n Effect.suspend(() => {\n const row = owned(state.authorizations, principal, id);\n return row === undefined ? notFound(\"authorization\", id) : Effect.succeed(row);\n });\n const connection = (principal: Principal.Interface, id: string) =>\n Effect.suspend(() => {\n const row = owned(state.connections, principal, id);\n return row === undefined ? notFound(\"connection\", id) : Effect.succeed(row);\n });\n const attachment = (principal: Principal.Interface, id: string) =>\n Effect.suspend(() => {\n const row = owned(state.attachments, principal, id);\n return row === undefined ? notFound(\"attachment\", id) : Effect.succeed(row);\n });\n const attempt = (principal: Principal.Interface, id: string) =>\n Effect.suspend(() => {\n const row = owned(state.attempts, principal, id);\n return row === undefined ? notFound(\"plan\", id) : Effect.succeed(row);\n });\n const attemptWhere = (\n principal: Principal.Interface,\n entity: Reason.NotFound[\"entity\"],\n id: string,\n predicate: (row: Storage.Attempt) => boolean,\n ) =>\n Effect.suspend(() => {\n const row = ownedRows(state.attempts, principal).find(predicate);\n return row === undefined ? notFound(entity, id) : Effect.succeed(row);\n });\n\n const finishRevocation = <E, R>(\n principal: Principal.Interface,\n row: Storage.Authorization,\n revoke: Effect.Effect<void, E, R>,\n ): Effect.Effect<void, Errors.DomainKitError | E, R> =>\n Effect.gen(function* () {\n yield* commit(\"authorizations.prepareRevocation\");\n state.authorizations.set(\n row.id,\n new Storage.Authorization({ ...row, revocation: \"pending\" }),\n );\n yield* revoke;\n yield* commit(\"authorizations.completeRevocation\");\n if (owned(state.authorizations, principal, row.id) !== undefined) {\n state.authorizations.delete(row.id);\n state.credentials.delete(row.id);\n }\n });\n\n return {\n authorizations: {\n upsert: ({ authorization: input, credential, expectedId }) =>\n write((principal) =>\n Effect.gen(function* () {\n if (input.ownerId !== principal.ownerId) {\n return yield* invalid(\"Authorization owner does not match the principal\", \"ownerId\");\n }\n if (expectedId !== undefined) {\n const current = yield* authorization(principal, expectedId);\n if (current.revocation !== \"active\") {\n return yield* Errors.fail(new Reason.Busy({ key: `authorization:${expectedId}` }));\n }\n if (input.id !== expectedId) {\n return yield* invalid(\"Authorization id must match expectedId\", \"id\");\n }\n } else if (state.authorizations.has(input.id)) {\n return yield* invalid(`Authorization ${input.id} already exists`, \"id\");\n }\n yield* commit(\"authorizations.upsert\");\n state.authorizations.set(input.id, input);\n state.credentials.set(input.id, credential);\n return input;\n }),\n ),\n get: (id) => read((principal) => authorization(principal, id)),\n credential: (id) =>\n read((principal) =>\n authorization(principal, id).pipe(\n Effect.flatMap(() => {\n const row = state.credentials.get(id);\n return row === undefined ? notFound(\"authorization\", id) : Effect.succeed(row);\n }),\n ),\n ),\n rotate: (id, credential) =>\n write((principal) =>\n Effect.gen(function* () {\n yield* authorization(principal, id);\n yield* commit(\"authorizations.rotate\");\n state.credentials.set(id, credential);\n }),\n ),\n promoteCapabilities: (id, capabilities) =>\n write((principal) =>\n Effect.gen(function* () {\n const row = yield* authorization(principal, id);\n yield* commit(\"authorizations.promoteCapabilities\");\n state.authorizations.set(\n id,\n new Storage.Authorization({\n ...row,\n capabilities: [...new Set([...row.capabilities, ...capabilities])],\n }),\n );\n }),\n ),\n revoke: (id, revoke) =>\n read((principal) =>\n authorization(principal, id).pipe(\n Effect.flatMap((row) => finishRevocation(principal, row, revoke)),\n ),\n ),\n recoverRevocations: (revoke) =>\n read((principal) =>\n Effect.forEach(\n ownedRows(state.authorizations, principal).filter(\n (row) => row.revocation === \"pending\",\n ),\n (row) => finishRevocation(principal, row, revoke(row)),\n ).pipe(Effect.map((finished) => finished.length)),\n ),\n },\n connections: {\n create: (authorizationId) =>\n write((principal) =>\n Effect.gen(function* () {\n yield* authorization(principal, authorizationId);\n const row = new Storage.Connection({\n id: yield* fresh(\"conn\"),\n ownerId: principal.ownerId,\n authorizationId,\n createdAt: yield* DateTime.now,\n });\n yield* commit(\"connections.create\");\n state.connections.set(row.id, row);\n return row;\n }),\n ),\n get: (id) => read((principal) => connection(principal, id)),\n list: (filter) =>\n read((principal) =>\n Effect.sync(() =>\n ownedRows(state.connections, principal).filter(\n (row) =>\n filter?.provider === undefined ||\n state.authorizations.get(row.authorizationId)?.provider === filter.provider,\n ),\n ),\n ),\n remove: (id) =>\n write((principal) =>\n Effect.gen(function* () {\n yield* connection(principal, id);\n if (ownedRows(state.attachments, principal).some((row) => row.connectionId === id)) {\n return yield* invalid(`Connection ${id} still has attachments`, \"connectionId\");\n }\n yield* commit(\"connections.remove\");\n state.connections.delete(id);\n }),\n ),\n },\n attachments: {\n create: (input) =>\n write((principal) =>\n Effect.gen(function* () {\n yield* connection(principal, input.connectionId);\n if (\n ownedRows(state.attachments, principal).some((row) => row.domain === input.domain)\n ) {\n return yield* invalid(`${input.domain} is already attached`, \"domain\");\n }\n const row = new Storage.Attachment({\n id: yield* fresh(\"att\"),\n ownerId: principal.ownerId,\n connectionId: input.connectionId,\n domain: input.domain,\n zone: input.zone,\n label: input.label,\n target: input.target,\n createdAt: yield* DateTime.now,\n });\n yield* commit(\"attachments.create\");\n state.attachments.set(row.id, row);\n return row;\n }),\n ),\n get: (id) => read((principal) => attachment(principal, id)),\n byDomain: (domain) =>\n read((principal) =>\n Effect.sync(() =>\n Option.fromNullishOr(\n ownedRows(state.attachments, principal).find((row) => row.domain === domain),\n ),\n ),\n ),\n list: (connectionId) =>\n read((principal) =>\n connection(principal, connectionId).pipe(\n Effect.map(() =>\n ownedRows(state.attachments, principal).filter(\n (row) => row.connectionId === connectionId,\n ),\n ),\n ),\n ),\n remove: (id) =>\n write((principal) =>\n Effect.gen(function* () {\n yield* attachment(principal, id);\n yield* commit(\"attachments.remove\");\n state.attachments.delete(id);\n }),\n ),\n },\n continuations: {\n put: (continuation) =>\n write((principal) =>\n Effect.gen(function* () {\n if (continuation.ownerId !== principal.ownerId) {\n return yield* invalid(\"Continuation owner does not match the principal\", \"ownerId\");\n }\n yield* commit(\"continuations.put\");\n state.continuations.set(continuation.id, continuation);\n }),\n ),\n get: (id) =>\n read((principal) =>\n Effect.gen(function* () {\n const row = owned(state.continuations, principal, id);\n if (row === undefined) return yield* notFound(\"continuation\", id);\n const now = yield* DateTime.now;\n if (DateTime.toEpochMillis(row.expiresAt) <= DateTime.toEpochMillis(now)) {\n return yield* Errors.fail(new Reason.Expired({ entity: \"continuation\", id }));\n }\n return row;\n }),\n ),\n header: (id) =>\n Effect.gen(function* () {\n // Unscoped on purpose, unlike every other read here: the provider callback has to learn\n // whose flow it is finishing before it can resolve a principal to scope by. Header\n // fields only; `payload` stays behind `get`.\n const row = state.continuations.get(id);\n if (row === undefined) return yield* notFound(\"continuation\", id);\n const now = yield* DateTime.now;\n if (DateTime.toEpochMillis(row.expiresAt) <= DateTime.toEpochMillis(now)) {\n return yield* Errors.fail(new Reason.Expired({ entity: \"continuation\", id }));\n }\n return new Storage.ContinuationHeader({\n ownerId: row.ownerId,\n actorId: row.actorId,\n provider: row.provider,\n expiresAt: row.expiresAt,\n });\n }),\n consume: (id) =>\n write((principal) =>\n Effect.gen(function* () {\n const row = owned(state.continuations, principal, id);\n if (row === undefined) return yield* notFound(\"continuation\", id);\n yield* commit(\"continuations.consume\");\n state.continuations.delete(id);\n const now = yield* DateTime.now;\n if (DateTime.toEpochMillis(row.expiresAt) <= DateTime.toEpochMillis(now)) {\n return yield* Errors.fail(new Reason.Expired({ entity: \"continuation\", id }));\n }\n return row;\n }),\n ),\n },\n attempts: {\n create: (input) =>\n write((principal) =>\n Effect.gen(function* () {\n if (input.ownerId !== principal.ownerId) {\n return yield* invalid(\"Attempt owner does not match the principal\", \"ownerId\");\n }\n if (state.attempts.has(input.id)) {\n return yield* invalid(`Plan ${input.id} already exists`, \"id\");\n }\n yield* commit(\"attempts.create\");\n state.attempts.set(input.id, input);\n return input;\n }),\n ),\n get: (id) => read((principal) => attempt(principal, id)),\n byApproval: (id) =>\n read((principal) =>\n attemptWhere(principal, \"approval\", id, (row) => row.approval?.id === id),\n ),\n byReceipt: (id) =>\n read((principal) =>\n attemptWhere(principal, \"receipt\", id, (row) => row.receipt?.id === id),\n ),\n latest: (attachmentId, kind) =>\n read((principal) =>\n Effect.sync(() =>\n Option.fromNullishOr(\n ownedRows(state.attempts, principal)\n .filter((row) => row.attachmentId === attachmentId && row.kind === kind)\n .reduce<Storage.Attempt | undefined>(\n (best, row) =>\n best === undefined ||\n DateTime.toEpochMillis(row.plan.createdAt) >=\n DateTime.toEpochMillis(best.plan.createdAt)\n ? row\n : best,\n undefined,\n ),\n ),\n ),\n ),\n approve: (id, approval: Approval.Model) =>\n write((principal) =>\n Effect.gen(function* () {\n const row = yield* attempt(principal, id);\n if (row.approval?.id === approval.id) return row;\n if (row.status !== \"planned\") return yield* stale(row);\n const next = new Storage.Attempt({\n ...row,\n status: \"approved\",\n approval,\n updatedAt: yield* DateTime.now,\n });\n yield* commit(\"attempts.approve\");\n state.attempts.set(id, next);\n return next;\n }),\n ),\n reject: (id, input) =>\n write((principal) =>\n Effect.gen(function* () {\n const row = yield* attempt(principal, id);\n if (row.status === \"rejected\") return row;\n if (row.status === \"expired\") {\n return yield* Errors.fail(new Reason.Expired({ entity: \"plan\", id }));\n }\n if (row.status !== \"planned\" || row.plan.digest !== input.digest)\n return yield* stale(row);\n const now = yield* DateTime.now;\n const next = new Storage.Attempt({\n ...row,\n status: \"rejected\",\n rejection: { actorId: input.actorId, reason: input.reason, at: now },\n updatedAt: now,\n });\n yield* commit(\"attempts.reject\");\n state.attempts.set(id, next);\n return next;\n }),\n ),\n claim: (id, lease) =>\n write((principal) =>\n Effect.gen(function* () {\n const row = yield* attempt(principal, id);\n const now = yield* DateTime.now;\n if (row.status === \"applying\") {\n const held =\n row.leaseExpiresAt !== null &&\n DateTime.toEpochMillis(row.leaseExpiresAt) > DateTime.toEpochMillis(now);\n if (held) {\n return yield* Errors.fail(new Reason.Busy({ key: `apply:${id}` }));\n }\n } else if (row.status !== \"approved\" && row.status !== \"failed\") {\n return yield* stale(row);\n }\n const next = new Storage.Attempt({\n ...row,\n status: \"applying\",\n leaseExpiresAt: lease,\n failure: null,\n updatedAt: now,\n });\n yield* commit(\"attempts.claim\");\n state.attempts.set(id, next);\n return next;\n }),\n ),\n complete: (id, receipt: Receipt.Model) =>\n write((principal) =>\n Effect.gen(function* () {\n const row = yield* attempt(principal, id);\n if (row.status !== \"applying\") return yield* stale(row);\n const next = new Storage.Attempt({\n ...row,\n status: receipt.status,\n receipt,\n leaseExpiresAt: null,\n updatedAt: yield* DateTime.now,\n });\n yield* commit(\"attempts.complete\");\n state.attempts.set(id, next);\n return next;\n }),\n ),\n fail: (id, message) =>\n write((principal) =>\n Effect.gen(function* () {\n const row = yield* attempt(principal, id);\n if (row.status !== \"applying\") return yield* stale(row);\n const next = new Storage.Attempt({\n ...row,\n status: \"failed\",\n failure: message,\n leaseExpiresAt: null,\n updatedAt: yield* DateTime.now,\n });\n yield* commit(\"attempts.fail\");\n state.attempts.set(id, next);\n return next;\n }),\n ),\n },\n readiness: {\n put: (readiness) =>\n write((principal) =>\n Effect.gen(function* () {\n if (readiness.ownerId !== principal.ownerId) {\n return yield* invalid(\"Readiness owner does not match the principal\", \"ownerId\");\n }\n if (readiness.attachmentId !== null)\n yield* attachment(principal, readiness.attachmentId);\n yield* commit(\"readiness.put\");\n state.readiness.set(`${principal.ownerId}:${readiness.domain}`, readiness);\n }),\n ),\n get: (domain) =>\n read((principal) =>\n Effect.sync(() =>\n Option.fromNullishOr(state.readiness.get(`${principal.ownerId}:${domain}`)),\n ),\n ),\n },\n withLock: (key, effect) =>\n Effect.flatMap(Principal.Service, (principal) => {\n const scoped = `${principal.ownerId}:${key}`;\n return Effect.acquireRelease(\n Effect.suspend(() => {\n if (state.locks.has(scoped)) {\n return Errors.fail(new Reason.Busy({ key }));\n }\n state.locks.add(scoped);\n return Effect.void;\n }),\n () => Effect.sync(() => void state.locks.delete(scoped)),\n ).pipe(\n Effect.flatMap(() => effect),\n Effect.scoped,\n );\n }),\n };\n}\n\nconst stale = (row: Storage.Attempt) =>\n Errors.fail(new Reason.Stale({ planId: Plan.PlanId.make(row.id), digest: row.plan.digest }));\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"storage-memory.mjs","names":["Errors.fail","Reason.NotFound","Reason.InvalidInput","Principal.Service","Reason.BatchStale","Storage.batchStatusOf","Storage.Batch","Storage.Authorization","Reason.Busy","Storage.Connection","Storage.Attachment","Reason.Expired","Storage.ContinuationHeader","Storage.Attempt","Storage.BatchItem","Reason.Stale"],"sources":["../../src/internal/storage-memory.ts"],"sourcesContent":["import { DateTime, Effect, Option, Semaphore } from \"effect\";\n\nimport type * as Approval from \"../Approval.ts\";\nimport * as Errors from \"./error.ts\";\nimport * as Reason from \"../Reason.ts\";\nimport * as Plan from \"../Plan.ts\";\nimport * as Principal from \"../Principal.ts\";\nimport type * as Receipt from \"../Receipt.ts\";\nimport * as Storage from \"../Storage.ts\";\nimport { fresh } from \"./ids.ts\";\n\ninterface State {\n readonly authorizations: Map<string, Storage.Authorization>;\n readonly credentials: Map<string, Storage.Credential>;\n readonly connections: Map<string, Storage.Connection>;\n readonly attachments: Map<string, Storage.Attachment>;\n readonly continuations: Map<string, Storage.Continuation>;\n readonly attempts: Map<string, Storage.Attempt>;\n readonly batches: Map<string, Storage.Batch>;\n /** Items by batch id, kept in `position` order. */\n readonly batchItems: Map<string, Array<Storage.BatchItem>>;\n readonly readiness: Map<string, Storage.Readiness>;\n readonly locks: Set<string>;\n}\n\nconst notFound = (entity: Reason.NotFound[\"entity\"], id: string) =>\n Errors.fail(new Reason.NotFound({ entity, id }));\nconst invalid = (message: string, field?: string) =>\n Errors.fail(new Reason.InvalidInput({ message, ...(field === undefined ? {} : { field }) }));\n\nexport function makeMemory(options: Storage.MemoryOptions = {}): Storage.Interface {\n const state: State = {\n authorizations: new Map(),\n credentials: new Map(),\n connections: new Map(),\n attachments: new Map(),\n continuations: new Map(),\n attempts: new Map(),\n batches: new Map(),\n batchItems: new Map(),\n readiness: new Map(),\n locks: new Set(),\n };\n const mutations = Semaphore.makeUnsafe(1);\n const commit = (operation: string) =>\n options.beforeCommit === undefined ? Effect.void : options.beforeCommit(operation);\n\n const read = <A, E, R = never>(\n run: (principal: Principal.Interface) => Effect.Effect<A, E, R>,\n ): Effect.Effect<A, E, Principal.Service | R> => Effect.flatMap(Principal.Service, run);\n const write = <A, E>(\n run: (principal: Principal.Interface) => Effect.Effect<A, E>,\n ): Effect.Effect<A, E, Principal.Service> =>\n Effect.flatMap(Principal.Service, (principal) => mutations.withPermit(run(principal)));\n\n const owned = <Row extends { readonly ownerId: string }>(\n rows: Map<string, Row>,\n principal: Principal.Interface,\n id: string,\n ): Row | undefined => {\n const row = rows.get(id);\n return row !== undefined && row.ownerId === principal.ownerId ? row : undefined;\n };\n const ownedRows = <Row extends { readonly ownerId: string }>(\n rows: Map<string, Row>,\n principal: Principal.Interface,\n ) => [...rows.values()].filter((row) => row.ownerId === principal.ownerId);\n\n const authorization = (principal: Principal.Interface, id: string) =>\n Effect.suspend(() => {\n const row = owned(state.authorizations, principal, id);\n return row === undefined ? notFound(\"authorization\", id) : Effect.succeed(row);\n });\n const connection = (principal: Principal.Interface, id: string) =>\n Effect.suspend(() => {\n const row = owned(state.connections, principal, id);\n return row === undefined ? notFound(\"connection\", id) : Effect.succeed(row);\n });\n const attachment = (principal: Principal.Interface, id: string) =>\n Effect.suspend(() => {\n const row = owned(state.attachments, principal, id);\n return row === undefined ? notFound(\"attachment\", id) : Effect.succeed(row);\n });\n const attempt = (principal: Principal.Interface, id: string) =>\n Effect.suspend(() => {\n const row = owned(state.attempts, principal, id);\n return row === undefined ? notFound(\"plan\", id) : Effect.succeed(row);\n });\n const attemptWhere = (\n principal: Principal.Interface,\n entity: Reason.NotFound[\"entity\"],\n id: string,\n predicate: (row: Storage.Attempt) => boolean,\n ) =>\n Effect.suspend(() => {\n const row = ownedRows(state.attempts, principal).find(predicate);\n return row === undefined ? notFound(entity, id) : Effect.succeed(row);\n });\n\n const batch = (principal: Principal.Interface, id: string) =>\n Effect.suspend(() => {\n const row = owned(state.batches, principal, id);\n return row === undefined ? notFound(\"batch\", id) : Effect.succeed(row);\n });\n\n const itemsOf = (batchId: string): ReadonlyArray<Storage.BatchItem> =>\n [...(state.batchItems.get(batchId) ?? [])].sort(\n (left, right) => left.position - right.position,\n );\n\n const aggregateOf = (row: Storage.Batch): Storage.BatchAggregate => ({\n batch: row,\n items: itemsOf(row.id),\n });\n\n const batchStale = (row: Storage.Batch) =>\n Errors.fail(new Reason.BatchStale({ batchId: row.id, status: row.status, digest: row.digest }));\n\n const statusOf = (row: Storage.Batch, items: ReadonlyArray<Storage.BatchItem>) =>\n Storage.batchStatusOf({\n approved: row.approval !== null,\n rejected: row.rejection !== null,\n items: items.map((item) =>\n item.attemptId === null ? null : (state.attempts.get(item.attemptId)?.status ?? null),\n ),\n });\n\n /** Store `row` with its status recomputed from the items' attempts, and return the aggregate. */\n const settle = (row: Storage.Batch, now: DateTime.Utc): Storage.BatchAggregate => {\n const items = itemsOf(row.id);\n const status = statusOf(row, items);\n const next = new Storage.Batch({\n ...row,\n status,\n completedAt: status === \"complete\" || status === \"rejected\" ? now : null,\n updatedAt: now,\n });\n state.batches.set(row.id, next);\n return { batch: next, items };\n };\n\n const replaceItem = (item: Storage.BatchItem) => {\n const items = state.batchItems.get(item.batchId) ?? [];\n const index = items.findIndex((row) => row.attachmentId === item.attachmentId);\n if (index >= 0) items[index] = item;\n };\n\n /** Only a batch still being planned accepts plan state, an approval, or a rejection. */\n const openForPlanning = (row: Storage.Batch) =>\n row.status === \"planning\" || row.status === \"planned\" ? Effect.void : batchStale(row);\n\n const itemAt = (row: Storage.Batch, attachmentId: string) =>\n Effect.suspend(() => {\n const item = itemsOf(row.id).find((entry) => entry.attachmentId === attachmentId);\n return item === undefined ? notFound(\"attachment\", attachmentId) : Effect.succeed(item);\n });\n\n const finishRevocation = <E, R>(\n principal: Principal.Interface,\n row: Storage.Authorization,\n revoke: Effect.Effect<void, E, R>,\n ): Effect.Effect<void, Errors.DomainKitError | E, R> =>\n Effect.gen(function* () {\n yield* commit(\"authorizations.prepareRevocation\");\n state.authorizations.set(\n row.id,\n new Storage.Authorization({ ...row, revocation: \"pending\" }),\n );\n yield* revoke;\n yield* commit(\"authorizations.completeRevocation\");\n if (owned(state.authorizations, principal, row.id) !== undefined) {\n state.authorizations.delete(row.id);\n state.credentials.delete(row.id);\n }\n });\n\n return {\n authorizations: {\n upsert: ({ authorization: input, credential, expectedId }) =>\n write((principal) =>\n Effect.gen(function* () {\n if (input.ownerId !== principal.ownerId) {\n return yield* invalid(\"Authorization owner does not match the principal\", \"ownerId\");\n }\n if (expectedId !== undefined) {\n const current = yield* authorization(principal, expectedId);\n if (current.revocation !== \"active\") {\n return yield* Errors.fail(new Reason.Busy({ key: `authorization:${expectedId}` }));\n }\n if (input.id !== expectedId) {\n return yield* invalid(\"Authorization id must match expectedId\", \"id\");\n }\n } else if (state.authorizations.has(input.id)) {\n return yield* invalid(`Authorization ${input.id} already exists`, \"id\");\n }\n yield* commit(\"authorizations.upsert\");\n state.authorizations.set(input.id, input);\n state.credentials.set(input.id, credential);\n return input;\n }),\n ),\n get: (id) => read((principal) => authorization(principal, id)),\n credential: (id) =>\n read((principal) =>\n authorization(principal, id).pipe(\n Effect.flatMap(() => {\n const row = state.credentials.get(id);\n return row === undefined ? notFound(\"authorization\", id) : Effect.succeed(row);\n }),\n ),\n ),\n rotate: (id, credential) =>\n write((principal) =>\n Effect.gen(function* () {\n yield* authorization(principal, id);\n yield* commit(\"authorizations.rotate\");\n state.credentials.set(id, credential);\n }),\n ),\n promoteCapabilities: (id, capabilities) =>\n write((principal) =>\n Effect.gen(function* () {\n const row = yield* authorization(principal, id);\n yield* commit(\"authorizations.promoteCapabilities\");\n state.authorizations.set(\n id,\n new Storage.Authorization({\n ...row,\n capabilities: [...new Set([...row.capabilities, ...capabilities])],\n }),\n );\n }),\n ),\n revoke: (id, revoke) =>\n read((principal) =>\n authorization(principal, id).pipe(\n Effect.flatMap((row) => finishRevocation(principal, row, revoke)),\n ),\n ),\n recoverRevocations: (revoke) =>\n read((principal) =>\n Effect.forEach(\n ownedRows(state.authorizations, principal).filter(\n (row) => row.revocation === \"pending\",\n ),\n (row) => finishRevocation(principal, row, revoke(row)),\n ).pipe(Effect.map((finished) => finished.length)),\n ),\n },\n connections: {\n create: (authorizationId) =>\n write((principal) =>\n Effect.gen(function* () {\n yield* authorization(principal, authorizationId);\n const row = new Storage.Connection({\n id: yield* fresh(\"conn\"),\n ownerId: principal.ownerId,\n authorizationId,\n createdAt: yield* DateTime.now,\n });\n yield* commit(\"connections.create\");\n state.connections.set(row.id, row);\n return row;\n }),\n ),\n get: (id) => read((principal) => connection(principal, id)),\n list: (filter) =>\n read((principal) =>\n Effect.sync(() =>\n ownedRows(state.connections, principal).filter(\n (row) =>\n filter?.provider === undefined ||\n state.authorizations.get(row.authorizationId)?.provider === filter.provider,\n ),\n ),\n ),\n remove: (id) =>\n write((principal) =>\n Effect.gen(function* () {\n yield* connection(principal, id);\n if (ownedRows(state.attachments, principal).some((row) => row.connectionId === id)) {\n return yield* invalid(`Connection ${id} still has attachments`, \"connectionId\");\n }\n yield* commit(\"connections.remove\");\n state.connections.delete(id);\n }),\n ),\n },\n attachments: {\n create: (input) =>\n write((principal) =>\n Effect.gen(function* () {\n yield* connection(principal, input.connectionId);\n if (\n ownedRows(state.attachments, principal).some((row) => row.domain === input.domain)\n ) {\n return yield* invalid(`${input.domain} is already attached`, \"domain\");\n }\n const row = new Storage.Attachment({\n id: yield* fresh(\"att\"),\n ownerId: principal.ownerId,\n connectionId: input.connectionId,\n domain: input.domain,\n zone: input.zone,\n label: input.label,\n target: input.target,\n createdAt: yield* DateTime.now,\n });\n yield* commit(\"attachments.create\");\n state.attachments.set(row.id, row);\n return row;\n }),\n ),\n get: (id) => read((principal) => attachment(principal, id)),\n byDomain: (domain) =>\n read((principal) =>\n Effect.sync(() =>\n Option.fromNullishOr(\n ownedRows(state.attachments, principal).find((row) => row.domain === domain),\n ),\n ),\n ),\n list: (connectionId) =>\n read((principal) =>\n connection(principal, connectionId).pipe(\n Effect.map(() =>\n ownedRows(state.attachments, principal).filter(\n (row) => row.connectionId === connectionId,\n ),\n ),\n ),\n ),\n remove: (id) =>\n write((principal) =>\n Effect.gen(function* () {\n yield* attachment(principal, id);\n yield* commit(\"attachments.remove\");\n state.attachments.delete(id);\n }),\n ),\n },\n continuations: {\n put: (continuation) =>\n write((principal) =>\n Effect.gen(function* () {\n if (continuation.ownerId !== principal.ownerId) {\n return yield* invalid(\"Continuation owner does not match the principal\", \"ownerId\");\n }\n yield* commit(\"continuations.put\");\n state.continuations.set(continuation.id, continuation);\n }),\n ),\n get: (id) =>\n read((principal) =>\n Effect.gen(function* () {\n const row = owned(state.continuations, principal, id);\n if (row === undefined) return yield* notFound(\"continuation\", id);\n const now = yield* DateTime.now;\n if (DateTime.toEpochMillis(row.expiresAt) <= DateTime.toEpochMillis(now)) {\n return yield* Errors.fail(new Reason.Expired({ entity: \"continuation\", id }));\n }\n return row;\n }),\n ),\n header: (id) =>\n Effect.gen(function* () {\n // Unscoped on purpose, unlike every other read here: the provider callback has to learn\n // whose flow it is finishing before it can resolve a principal to scope by. Header\n // fields only; `payload` stays behind `get`.\n const row = state.continuations.get(id);\n if (row === undefined) return yield* notFound(\"continuation\", id);\n const now = yield* DateTime.now;\n if (DateTime.toEpochMillis(row.expiresAt) <= DateTime.toEpochMillis(now)) {\n return yield* Errors.fail(new Reason.Expired({ entity: \"continuation\", id }));\n }\n return new Storage.ContinuationHeader({\n ownerId: row.ownerId,\n actorId: row.actorId,\n provider: row.provider,\n expiresAt: row.expiresAt,\n });\n }),\n consume: (id) =>\n write((principal) =>\n Effect.gen(function* () {\n const row = owned(state.continuations, principal, id);\n if (row === undefined) return yield* notFound(\"continuation\", id);\n yield* commit(\"continuations.consume\");\n state.continuations.delete(id);\n const now = yield* DateTime.now;\n if (DateTime.toEpochMillis(row.expiresAt) <= DateTime.toEpochMillis(now)) {\n return yield* Errors.fail(new Reason.Expired({ entity: \"continuation\", id }));\n }\n return row;\n }),\n ),\n },\n attempts: {\n create: (input) =>\n write((principal) =>\n Effect.gen(function* () {\n if (input.ownerId !== principal.ownerId) {\n return yield* invalid(\"Attempt owner does not match the principal\", \"ownerId\");\n }\n if (state.attempts.has(input.id)) {\n return yield* invalid(`Plan ${input.id} already exists`, \"id\");\n }\n yield* commit(\"attempts.create\");\n state.attempts.set(input.id, input);\n return input;\n }),\n ),\n get: (id) => read((principal) => attempt(principal, id)),\n byApproval: (id) =>\n read((principal) =>\n attemptWhere(principal, \"approval\", id, (row) => row.approval?.id === id),\n ),\n byReceipt: (id) =>\n read((principal) =>\n attemptWhere(principal, \"receipt\", id, (row) => row.receipt?.id === id),\n ),\n latest: (attachmentId, kind) =>\n read((principal) =>\n Effect.sync(() =>\n Option.fromNullishOr(\n ownedRows(state.attempts, principal)\n .filter((row) => row.attachmentId === attachmentId && row.kind === kind)\n .reduce<Storage.Attempt | undefined>(\n (best, row) =>\n best === undefined ||\n DateTime.toEpochMillis(row.plan.createdAt) >=\n DateTime.toEpochMillis(best.plan.createdAt)\n ? row\n : best,\n undefined,\n ),\n ),\n ),\n ),\n approve: (id, approval: Approval.Model) =>\n write((principal) =>\n Effect.gen(function* () {\n const row = yield* attempt(principal, id);\n if (row.approval?.id === approval.id) return row;\n if (row.status !== \"planned\") return yield* stale(row);\n const next = new Storage.Attempt({\n ...row,\n status: \"approved\",\n approval,\n updatedAt: yield* DateTime.now,\n });\n yield* commit(\"attempts.approve\");\n state.attempts.set(id, next);\n return next;\n }),\n ),\n reject: (id, input) =>\n write((principal) =>\n Effect.gen(function* () {\n const row = yield* attempt(principal, id);\n if (row.status === \"rejected\") return row;\n if (row.status === \"expired\") {\n return yield* Errors.fail(new Reason.Expired({ entity: \"plan\", id }));\n }\n if (row.status !== \"planned\" || row.plan.digest !== input.digest)\n return yield* stale(row);\n const now = yield* DateTime.now;\n const next = new Storage.Attempt({\n ...row,\n status: \"rejected\",\n rejection: { actorId: input.actorId, reason: input.reason, at: now },\n updatedAt: now,\n });\n yield* commit(\"attempts.reject\");\n state.attempts.set(id, next);\n return next;\n }),\n ),\n claim: (id, lease) =>\n write((principal) =>\n Effect.gen(function* () {\n const row = yield* attempt(principal, id);\n const now = yield* DateTime.now;\n if (row.status === \"applying\") {\n const held =\n row.leaseExpiresAt !== null &&\n DateTime.toEpochMillis(row.leaseExpiresAt) > DateTime.toEpochMillis(now);\n if (held) {\n return yield* Errors.fail(new Reason.Busy({ key: `apply:${id}` }));\n }\n } else if (row.status !== \"approved\" && row.status !== \"failed\") {\n return yield* stale(row);\n }\n const next = new Storage.Attempt({\n ...row,\n status: \"applying\",\n leaseExpiresAt: lease,\n failure: null,\n updatedAt: now,\n });\n yield* commit(\"attempts.claim\");\n state.attempts.set(id, next);\n return next;\n }),\n ),\n complete: (id, receipt: Receipt.Model) =>\n write((principal) =>\n Effect.gen(function* () {\n const row = yield* attempt(principal, id);\n if (row.status !== \"applying\") return yield* stale(row);\n const next = new Storage.Attempt({\n ...row,\n status: receipt.status,\n receipt,\n leaseExpiresAt: null,\n updatedAt: yield* DateTime.now,\n });\n yield* commit(\"attempts.complete\");\n state.attempts.set(id, next);\n return next;\n }),\n ),\n fail: (id, message) =>\n write((principal) =>\n Effect.gen(function* () {\n const row = yield* attempt(principal, id);\n if (row.status !== \"applying\") return yield* stale(row);\n const next = new Storage.Attempt({\n ...row,\n status: \"failed\",\n failure: message,\n leaseExpiresAt: null,\n updatedAt: yield* DateTime.now,\n });\n yield* commit(\"attempts.fail\");\n state.attempts.set(id, next);\n return next;\n }),\n ),\n },\n batches: {\n create: (input) =>\n write((principal) =>\n Effect.gen(function* () {\n const replay = ownedRows(state.batches, principal).find(\n (row) => row.idempotencyKey === input.idempotencyKey,\n );\n if (replay !== undefined) return aggregateOf(replay);\n if (input.attachmentIds.length === 0) {\n return yield* invalid(\"A batch needs at least one attachment\", \"attachmentIds\");\n }\n if (new Set(input.attachmentIds).size !== input.attachmentIds.length) {\n return yield* invalid(\"A batch holds one item per attachment\", \"attachmentIds\");\n }\n for (const attachmentId of input.attachmentIds) {\n yield* attachment(principal, attachmentId);\n }\n const now = yield* DateTime.now;\n const id = Storage.BatchId.make(yield* fresh(\"batch\"));\n const row = new Storage.Batch({\n id,\n ownerId: principal.ownerId,\n kind: input.kind,\n status: \"planning\",\n digest: null,\n approval: null,\n rejection: null,\n idempotencyKey: input.idempotencyKey,\n createdBy: principal.actorId,\n createdAt: now,\n updatedAt: now,\n completedAt: null,\n });\n yield* commit(\"batches.create\");\n state.batches.set(id, row);\n state.batchItems.set(\n id,\n input.attachmentIds.map(\n (attachmentId, position) =>\n new Storage.BatchItem({\n batchId: id,\n attachmentId,\n position,\n attemptId: null,\n planFailure: null,\n }),\n ),\n );\n return aggregateOf(row);\n }),\n ),\n get: (id) => read((principal) => Effect.map(batch(principal, id), aggregateOf)),\n byIdempotencyKey: (key) =>\n read((principal) =>\n Effect.sync(() =>\n Option.map(\n Option.fromNullishOr(\n ownedRows(state.batches, principal).find((row) => row.idempotencyKey === key),\n ),\n aggregateOf,\n ),\n ),\n ),\n listUnfinished: () =>\n read((principal) =>\n Effect.sync(() =>\n ownedRows(state.batches, principal)\n .filter((row) => row.status !== \"complete\" && row.status !== \"rejected\")\n .sort(\n (left, right) =>\n DateTime.toEpochMillis(right.updatedAt) - DateTime.toEpochMillis(left.updatedAt),\n )\n .map(aggregateOf),\n ),\n ),\n recordItemPlan: (id, attachmentId, attemptId) =>\n write((principal) =>\n Effect.gen(function* () {\n const row = yield* batch(principal, id);\n yield* openForPlanning(row);\n const item = yield* itemAt(row, attachmentId);\n const planned = yield* attempt(principal, attemptId);\n if (planned.attachmentId !== attachmentId || planned.kind !== row.kind) {\n return yield* invalid(\n `Plan ${attemptId} does not belong to attachment ${attachmentId} in this batch`,\n \"attemptId\",\n );\n }\n yield* commit(\"batches.recordItemPlan\");\n replaceItem(new Storage.BatchItem({ ...item, attemptId, planFailure: null }));\n return settle(row, yield* DateTime.now);\n }),\n ),\n recordItemPlanFailure: (id, attachmentId, message) =>\n write((principal) =>\n Effect.gen(function* () {\n const row = yield* batch(principal, id);\n yield* openForPlanning(row);\n const item = yield* itemAt(row, attachmentId);\n yield* commit(\"batches.recordItemPlanFailure\");\n replaceItem(new Storage.BatchItem({ ...item, attemptId: null, planFailure: message }));\n return settle(row, yield* DateTime.now);\n }),\n ),\n approve: (id, input) =>\n write((principal) =>\n Effect.gen(function* () {\n const row = yield* batch(principal, id);\n // Replaying the same approval is how a retried request stays safe.\n if (row.approval !== null) {\n return row.digest === input.digest ? aggregateOf(row) : yield* batchStale(row);\n }\n if (row.status !== \"planned\") return yield* batchStale(row);\n const items = itemsOf(id);\n const pairs: Array<{\n readonly attempt: Storage.Attempt;\n readonly approval: Approval.Model;\n }> = [];\n for (const item of items) {\n const supplied = input.approvals.find(\n (entry) => entry.attachmentId === item.attachmentId,\n );\n if (supplied === undefined) {\n return yield* invalid(\n `Batch ${id} has no approval for attachment ${item.attachmentId}`,\n \"approvals\",\n );\n }\n // The item has to still point at the attempt this approval names: anything else\n // means the batch was re-planned after the principal reviewed it.\n const stored =\n item.attemptId === null ? undefined : state.attempts.get(item.attemptId);\n if (stored === undefined || stored.id !== supplied.approval.planId) {\n return yield* batchStale(row);\n }\n // The attempt has to still be open: one declined or applied through the\n // single-domain API is not something a batch approval may reopen.\n if (stored.approval?.id !== supplied.approval.id && stored.status !== \"planned\") {\n return yield* stale(stored);\n }\n pairs.push({ attempt: stored, approval: supplied.approval });\n }\n const now = yield* DateTime.now;\n yield* commit(\"batches.approve\");\n for (const { attempt: stored, approval } of pairs) {\n if (stored.approval?.id === approval.id) continue;\n state.attempts.set(\n stored.id,\n new Storage.Attempt({\n ...stored,\n status: \"approved\",\n approval,\n updatedAt: now,\n }),\n );\n }\n return settle(\n new Storage.Batch({\n ...row,\n digest: input.digest,\n approval: { actorId: input.actorId, at: now },\n }),\n now,\n );\n }),\n ),\n reject: (id, input) =>\n write((principal) =>\n Effect.gen(function* () {\n const row = yield* batch(principal, id);\n // Terminal: a second rejection is the same outcome, so it returns the first one.\n if (row.rejection !== null) return aggregateOf(row);\n yield* openForPlanning(row);\n const now = yield* DateTime.now;\n yield* commit(\"batches.reject\");\n for (const item of itemsOf(id)) {\n const stored =\n item.attemptId === null ? undefined : state.attempts.get(item.attemptId);\n if (stored === undefined || stored.status !== \"planned\") continue;\n state.attempts.set(\n stored.id,\n new Storage.Attempt({\n ...stored,\n status: \"rejected\",\n rejection: { actorId: input.actorId, reason: input.reason, at: now },\n updatedAt: now,\n }),\n );\n }\n return settle(\n new Storage.Batch({\n ...row,\n rejection: { actorId: input.actorId, reason: input.reason, at: now },\n }),\n now,\n );\n }),\n ),\n refresh: (id) =>\n write((principal) =>\n Effect.gen(function* () {\n const row = yield* batch(principal, id);\n yield* commit(\"batches.refresh\");\n return settle(row, yield* DateTime.now);\n }),\n ),\n },\n readiness: {\n put: (readiness) =>\n write((principal) =>\n Effect.gen(function* () {\n if (readiness.ownerId !== principal.ownerId) {\n return yield* invalid(\"Readiness owner does not match the principal\", \"ownerId\");\n }\n if (readiness.attachmentId !== null)\n yield* attachment(principal, readiness.attachmentId);\n yield* commit(\"readiness.put\");\n state.readiness.set(`${principal.ownerId}:${readiness.domain}`, readiness);\n }),\n ),\n get: (domain) =>\n read((principal) =>\n Effect.sync(() =>\n Option.fromNullishOr(state.readiness.get(`${principal.ownerId}:${domain}`)),\n ),\n ),\n },\n withLock: (key, effect) =>\n Effect.flatMap(Principal.Service, (principal) => {\n const scoped = `${principal.ownerId}:${key}`;\n return Effect.acquireRelease(\n Effect.suspend(() => {\n if (state.locks.has(scoped)) {\n return Errors.fail(new Reason.Busy({ key }));\n }\n state.locks.add(scoped);\n return Effect.void;\n }),\n () => Effect.sync(() => void state.locks.delete(scoped)),\n ).pipe(\n Effect.flatMap(() => effect),\n Effect.scoped,\n );\n }),\n };\n}\n\nconst stale = (row: Storage.Attempt) =>\n Errors.fail(new Reason.Stale({ planId: Plan.PlanId.make(row.id), digest: row.plan.digest }));\n"],"mappings":"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"}
|
package/dist/package.mjs
CHANGED
package/dist/types/Connect.d.ts
CHANGED
|
@@ -111,7 +111,7 @@ export interface Snapshot {
|
|
|
111
111
|
readonly domain: string;
|
|
112
112
|
readonly attachment: Storage.Attachment | null;
|
|
113
113
|
readonly connection: Storage.Connection | null;
|
|
114
|
-
readonly authorization: Pick<Storage.Authorization, "id" | "provider" | "method" | "capabilities" | "revocation"> | null;
|
|
114
|
+
readonly authorization: Pick<Storage.Authorization, "id" | "provider" | "method" | "capabilities" | "revocation" | "label"> | null;
|
|
115
115
|
readonly lastReceiptId: string | null;
|
|
116
116
|
/** How many domains this domain's connection serves, including this one; `0` without one. */
|
|
117
117
|
readonly connectionDomains: number;
|
|
@@ -425,6 +425,8 @@ export interface Zone {
|
|
|
425
425
|
export interface ZoneConnection {
|
|
426
426
|
readonly connectionId: string;
|
|
427
427
|
readonly provider: string;
|
|
428
|
+
/** What the provider called the account at connect time; `null` when it named none. */
|
|
429
|
+
readonly label: string | null;
|
|
428
430
|
readonly status: "connected" | "reconnect";
|
|
429
431
|
}
|
|
430
432
|
/** Every zone the principal's connections reach, and how each connection is standing. */
|
package/dist/types/Provider.d.ts
CHANGED
|
@@ -128,6 +128,12 @@ export interface Credential {
|
|
|
128
128
|
/** A credential the provider just issued or refreshed; `null` expiry means it does not expire. */
|
|
129
129
|
export interface IssuedCredential extends Credential {
|
|
130
130
|
readonly expiresAt: DateTime.Utc | null;
|
|
131
|
+
/**
|
|
132
|
+
* What the provider calls the account this credential reaches, so a UI names the connection
|
|
133
|
+
* without a provider call. Omit it, or answer `null`, when the provider cannot name one: a
|
|
134
|
+
* credential that spans several accounts has no single name.
|
|
135
|
+
*/
|
|
136
|
+
readonly label?: string | null;
|
|
131
137
|
}
|
|
132
138
|
/** Raw token-method input keyed by the provider's declared field names. */
|
|
133
139
|
export type TokenValues = Readonly<Record<string, Redacted.Redacted<string> | undefined>>;
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
* plan in one request, collect consent in another, and apply in a third, and a retry of any step
|
|
4
4
|
* is idempotent.
|
|
5
5
|
*/
|
|
6
|
-
import { Context, Effect, Layer } from "effect";
|
|
6
|
+
import { Context, type DateTime, Effect, Layer } from "effect";
|
|
7
7
|
import type * as Approval from "./Approval.ts";
|
|
8
8
|
import * as Connect from "./Connect.ts";
|
|
9
9
|
import type * as DnsRecord from "./DnsRecord.ts";
|
|
10
10
|
import * as Errors from "./internal/error.ts";
|
|
11
11
|
import * as Attempts from "./internal/attempts.ts";
|
|
12
|
-
import
|
|
13
|
-
import
|
|
12
|
+
import * as Plan from "./Plan.ts";
|
|
13
|
+
import * as Principal from "./Principal.ts";
|
|
14
14
|
import type * as Receipt from "./Receipt.ts";
|
|
15
15
|
import * as Storage from "./Storage.ts";
|
|
16
16
|
type Fx<A> = Effect.Effect<A, Errors.DomainKitError, Principal.Service>;
|
|
@@ -24,6 +24,101 @@ export interface Attempt {
|
|
|
24
24
|
export interface RejectOptions {
|
|
25
25
|
readonly reason?: string;
|
|
26
26
|
}
|
|
27
|
+
/** One domain to plan inside a batch. The domain must already be attached. */
|
|
28
|
+
export interface BatchInput {
|
|
29
|
+
readonly domain: string;
|
|
30
|
+
readonly requirements: ReadonlyArray<DnsRecord.Model>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* One domain's place in a batch, with whatever its attempt holds.
|
|
34
|
+
*
|
|
35
|
+
* Everything but `planFailure` is read from the attempt the item points at, so a batch never
|
|
36
|
+
* shows a customer a plan, receipt, or failure that disagrees with the attempt it came from.
|
|
37
|
+
*/
|
|
38
|
+
export interface BatchItem {
|
|
39
|
+
readonly attachmentId: string;
|
|
40
|
+
readonly position: number;
|
|
41
|
+
/** Null while the item has no plan; `planFailure` then says why. */
|
|
42
|
+
readonly plan: Plan.Model | null;
|
|
43
|
+
readonly status: Storage.AttemptStatus | null;
|
|
44
|
+
readonly approval: Approval.Model | null;
|
|
45
|
+
readonly receipt: Receipt.Model | null;
|
|
46
|
+
readonly rejection: Storage.Rejection | null;
|
|
47
|
+
/** Why the last apply stopped before any write. */
|
|
48
|
+
readonly failure: string | null;
|
|
49
|
+
/** Why the last planning pass could not build this item's plan. */
|
|
50
|
+
readonly planFailure: string | null;
|
|
51
|
+
}
|
|
52
|
+
/** Many domains planned together, approved once, and applied with bounded concurrency. */
|
|
53
|
+
export interface Batch {
|
|
54
|
+
readonly id: Storage.BatchId;
|
|
55
|
+
readonly status: Storage.BatchStatus;
|
|
56
|
+
/**
|
|
57
|
+
* What `approve` binds: SHA-256 over the items' sorted `attachmentId:planDigest` pairs. The
|
|
58
|
+
* digest the batch was approved at once it is approved, the digest its current plans produce
|
|
59
|
+
* before that, and null while any item is still unplanned.
|
|
60
|
+
*/
|
|
61
|
+
readonly digest: Plan.Digest | null;
|
|
62
|
+
readonly approval: Storage.BatchApproval | null;
|
|
63
|
+
readonly rejection: Storage.Rejection | null;
|
|
64
|
+
readonly createdAt: DateTime.Utc;
|
|
65
|
+
readonly updatedAt: DateTime.Utc;
|
|
66
|
+
readonly completedAt: DateTime.Utc | null;
|
|
67
|
+
readonly items: ReadonlyArray<BatchItem>;
|
|
68
|
+
}
|
|
69
|
+
/** A batch without its plans, for the index a host renders as a banner. */
|
|
70
|
+
export interface BatchSummary {
|
|
71
|
+
readonly id: Storage.BatchId;
|
|
72
|
+
readonly status: Storage.BatchStatus;
|
|
73
|
+
readonly itemCount: number;
|
|
74
|
+
readonly createdAt: DateTime.Utc;
|
|
75
|
+
readonly updatedAt: DateTime.Utc;
|
|
76
|
+
readonly completedAt: DateTime.Utc | null;
|
|
77
|
+
}
|
|
78
|
+
export interface BatchInterface {
|
|
79
|
+
/**
|
|
80
|
+
* Plan every domain in `items` as one batch, with `Policy.batchConcurrency` in flight.
|
|
81
|
+
*
|
|
82
|
+
* `idempotencyKey` is unique per owner: a retried create returns the batch the first call made,
|
|
83
|
+
* plans whatever is still unplanned, and ignores items that batch does not hold. An item whose
|
|
84
|
+
* plan fails records the reason and leaves the batch `planning` for `resumePlanning`.
|
|
85
|
+
*/
|
|
86
|
+
readonly create: (input: {
|
|
87
|
+
readonly items: ReadonlyArray<BatchInput>;
|
|
88
|
+
readonly idempotencyKey: string;
|
|
89
|
+
}) => Fx<Batch>;
|
|
90
|
+
/**
|
|
91
|
+
* Plan the items that still have none, with the requirements supplied again. A batch stores
|
|
92
|
+
* pointers, not requirements, so the host owns them across the retry.
|
|
93
|
+
*/
|
|
94
|
+
readonly resumePlanning: (id: Storage.BatchId, input: {
|
|
95
|
+
readonly items: ReadonlyArray<BatchInput>;
|
|
96
|
+
}) => Fx<Batch>;
|
|
97
|
+
/**
|
|
98
|
+
* Record the principal's consent to the whole batch. Fails `BatchStale` unless `digest` is the
|
|
99
|
+
* one the batch's current plans produce. Approving an approved batch at its digest returns it.
|
|
100
|
+
*/
|
|
101
|
+
readonly approve: (id: Storage.BatchId, options: {
|
|
102
|
+
readonly digest: Plan.Digest;
|
|
103
|
+
}) => Fx<Batch>;
|
|
104
|
+
/**
|
|
105
|
+
* Apply every approved item that has no receipt yet, with `Policy.batchConcurrency` in flight
|
|
106
|
+
* and each item under its own attempt lease. An item another apply holds is skipped this round,
|
|
107
|
+
* and an item that fails records its failure on its attempt rather than stopping the others, so
|
|
108
|
+
* calling `apply` again resumes the batch. Applying a complete batch returns it unchanged.
|
|
109
|
+
*/
|
|
110
|
+
readonly apply: (id: Storage.BatchId) => Fx<Batch>;
|
|
111
|
+
/**
|
|
112
|
+
* Decline the batch and every plan under it; terminal. Rejecting again returns the same batch;
|
|
113
|
+
* a batch that was approved fails `BatchStale`.
|
|
114
|
+
*/
|
|
115
|
+
readonly reject: (id: Storage.BatchId, options?: RejectOptions) => Fx<Batch>;
|
|
116
|
+
readonly get: (id: Storage.BatchId) => Fx<Batch>;
|
|
117
|
+
/** Every batch this owner still owes a move on, most recently touched first. */
|
|
118
|
+
readonly list: (filter: {
|
|
119
|
+
readonly unfinished: true;
|
|
120
|
+
}) => Fx<ReadonlyArray<BatchSummary>>;
|
|
121
|
+
}
|
|
27
122
|
export interface Interface {
|
|
28
123
|
/** Read provider state and build an additive plan. Fails `NotFound` when the domain is not attached. */
|
|
29
124
|
readonly plan: (input: {
|
|
@@ -47,6 +142,8 @@ export interface Interface {
|
|
|
47
142
|
readonly apply: (approval: Approval.Model | Approval.ApprovalId) => Fx<Receipt.Model>;
|
|
48
143
|
readonly get: (planId: Plan.PlanId) => Fx<Attempt>;
|
|
49
144
|
readonly latest: (domain: string) => Fx<Attempt | null>;
|
|
145
|
+
/** Many domains as one reviewable unit: plan together, approve once, apply bounded. */
|
|
146
|
+
readonly batch: BatchInterface;
|
|
50
147
|
}
|
|
51
148
|
declare const Service_base: Context.ServiceClass<Service, "@domainkit/Provision", Interface>;
|
|
52
149
|
export declare class Service extends Service_base {
|
|
@@ -56,6 +153,8 @@ export interface PolicyShape {
|
|
|
56
153
|
readonly planTtlMs: number;
|
|
57
154
|
/** Apply lease; a crashed apply can be retried after this. Default 2 minutes. */
|
|
58
155
|
readonly applyLeaseMs: number;
|
|
156
|
+
/** How many of a batch's domains are planned or applied at once. Default 4. */
|
|
157
|
+
readonly batchConcurrency: number;
|
|
59
158
|
}
|
|
60
159
|
export declare const defaults: PolicyShape;
|
|
61
160
|
declare const Policy_base: Context.Reference<PolicyShape>;
|
|
@@ -72,4 +171,26 @@ export declare const reject: (plan: Plan.Model | (string & import("effect/Brand"
|
|
|
72
171
|
export declare const apply: (approval: Approval.Model | (string & import("effect/Brand").Brand<"@domainkit/ApprovalId">)) => Effect.Effect<Receipt.Model, Errors.DomainKitError, Principal.Service | Service>;
|
|
73
172
|
export declare const get: (planId: string & import("effect/Brand").Brand<"@domainkit/PlanId">) => Effect.Effect<Attempt, Errors.DomainKitError, Principal.Service | Service>;
|
|
74
173
|
export declare const latest: (domain: string) => Effect.Effect<Attempt | null, Errors.DomainKitError, Principal.Service | Service>;
|
|
174
|
+
/**
|
|
175
|
+
* Many domains as one reviewable unit. Every operation needs `Provision.Service` and `Principal`,
|
|
176
|
+
* exactly like the single-domain accessors above.
|
|
177
|
+
*/
|
|
178
|
+
export declare const batch: {
|
|
179
|
+
create: (input: {
|
|
180
|
+
readonly items: ReadonlyArray<BatchInput>;
|
|
181
|
+
readonly idempotencyKey: string;
|
|
182
|
+
}) => Effect.Effect<Batch, Errors.DomainKitError, Principal.Service | Service>;
|
|
183
|
+
resumePlanning: (id: string & import("effect/Brand").Brand<"@domainkit/BatchId">, input: {
|
|
184
|
+
readonly items: ReadonlyArray<BatchInput>;
|
|
185
|
+
}) => Effect.Effect<Batch, Errors.DomainKitError, Principal.Service | Service>;
|
|
186
|
+
approve: (id: string & import("effect/Brand").Brand<"@domainkit/BatchId">, options: {
|
|
187
|
+
readonly digest: Plan.Digest;
|
|
188
|
+
}) => Effect.Effect<Batch, Errors.DomainKitError, Principal.Service | Service>;
|
|
189
|
+
apply: (id: string & import("effect/Brand").Brand<"@domainkit/BatchId">) => Effect.Effect<Batch, Errors.DomainKitError, Principal.Service | Service>;
|
|
190
|
+
reject: (id: string & import("effect/Brand").Brand<"@domainkit/BatchId">, options?: RejectOptions | undefined) => Effect.Effect<Batch, Errors.DomainKitError, Principal.Service | Service>;
|
|
191
|
+
get: (id: string & import("effect/Brand").Brand<"@domainkit/BatchId">) => Effect.Effect<Batch, Errors.DomainKitError, Principal.Service | Service>;
|
|
192
|
+
list: (filter: {
|
|
193
|
+
readonly unfinished: true;
|
|
194
|
+
}) => Effect.Effect<readonly BatchSummary[], Errors.DomainKitError, Principal.Service | Service>;
|
|
195
|
+
};
|
|
75
196
|
export {};
|