@frockbot/plugin-shell 0.1.3 → 0.1.4
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/package.json +28 -28
- package/src/backend-composition.ts +4 -4
- package/src/backend-configuration.test.ts +366 -1451
- package/src/backend-execution.ts +0 -4
- package/src/backend-image.test.ts +8 -8
- package/src/backend-image.ts +13 -13
- package/src/backend-isolate.test.ts +181 -35
- package/src/backend-isolate.ts +110 -72
- package/src/backend-recovery-integration.test.ts +17 -67
- package/src/backend-routines.ts +1 -1
- package/src/backend.ts +335 -1659
- package/src/client/FrockBotApp.vue +6 -73
- package/src/client/index.test.ts +320 -380
- package/src/client/index.ts +85 -256
- package/src/client/model-presentation.test.ts +21 -8
- package/src/client/model-presentation.ts +14 -7
- package/src/client/styles.css +0 -27
- package/src/settings-links.test.ts +2 -10
- package/src/settings-links.ts +1 -13
- package/src/shared.ts +0 -13
- package/src/backend-assignment.test.ts +0 -161
- package/src/backend-assignment.ts +0 -274
package/src/shared.ts
CHANGED
|
@@ -7,8 +7,6 @@ import type {
|
|
|
7
7
|
BotProfile,
|
|
8
8
|
BotProfilePatchV1,
|
|
9
9
|
BotSettingsViewV1,
|
|
10
|
-
CapabilityAssignmentView,
|
|
11
|
-
ModelAssignment,
|
|
12
10
|
UserSettingsViewV1,
|
|
13
11
|
} from "@frockbot/configuration-core";
|
|
14
12
|
import type {
|
|
@@ -206,19 +204,8 @@ export interface FrockBotWebData {
|
|
|
206
204
|
namedBy?: BotNameProvenanceV1,
|
|
207
205
|
): Promise<void>;
|
|
208
206
|
saveBotNotifications(notifications: BotNotificationPolicy): Promise<void>;
|
|
209
|
-
assignCapability(
|
|
210
|
-
assignment: Omit<CapabilityAssignmentView, "state">,
|
|
211
|
-
): Promise<void>;
|
|
212
|
-
replaceCapability(
|
|
213
|
-
assignment: Omit<CapabilityAssignmentView, "state">,
|
|
214
|
-
): Promise<void>;
|
|
215
|
-
unassignCapability(assignmentId: string): Promise<void>;
|
|
216
|
-
saveBotModel(model: ModelAssignment): Promise<void>;
|
|
217
|
-
clearBotModel(): Promise<void>;
|
|
218
207
|
loadUserSettings(): Promise<void>;
|
|
219
208
|
saveUserProfile(profile: { name: string; email?: string }): Promise<void>;
|
|
220
|
-
/** The model every Bot uses unless it overrides it. */
|
|
221
|
-
saveDefaultModel(model: ModelAssignment | undefined): Promise<void>;
|
|
222
209
|
loadPluginCatalog(): Promise<void>;
|
|
223
210
|
/** Refreshes {@link FrockBotWebData.mcpServers}. */
|
|
224
211
|
loadMcpServers(): Promise<void>;
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
nextAssignmentPhase,
|
|
4
|
-
requireStoredAssignmentSaga,
|
|
5
|
-
settleAssignmentSaga,
|
|
6
|
-
type AssignmentSagaEffects,
|
|
7
|
-
type StoredAssignmentSaga,
|
|
8
|
-
} from "./backend-assignment.js";
|
|
9
|
-
|
|
10
|
-
function saga(
|
|
11
|
-
phase: StoredAssignmentSaga["phase"],
|
|
12
|
-
input: Partial<StoredAssignmentSaga> = {},
|
|
13
|
-
): StoredAssignmentSaga {
|
|
14
|
-
return {
|
|
15
|
-
schemaVersion: 1,
|
|
16
|
-
commandId: "command-1",
|
|
17
|
-
commandFingerprint: "configuration-command-v1:test",
|
|
18
|
-
userId: "user-1",
|
|
19
|
-
botId: "bot-1",
|
|
20
|
-
operation: "replacing",
|
|
21
|
-
assignmentId: "mail",
|
|
22
|
-
generation: "generation-1",
|
|
23
|
-
phase,
|
|
24
|
-
target: {
|
|
25
|
-
assignmentId: "mail",
|
|
26
|
-
packageId: "mail",
|
|
27
|
-
capabilityId: "send",
|
|
28
|
-
connectionId: "new-connection",
|
|
29
|
-
},
|
|
30
|
-
previous: {
|
|
31
|
-
assignmentId: "mail",
|
|
32
|
-
packageId: "mail",
|
|
33
|
-
capabilityId: "send",
|
|
34
|
-
connectionId: "old-connection",
|
|
35
|
-
state: "enabled",
|
|
36
|
-
},
|
|
37
|
-
previousGeneration: "old-generation",
|
|
38
|
-
deadlineAt: Date.now() + 60_000,
|
|
39
|
-
...input,
|
|
40
|
-
acceptedReceipt: input.acceptedReceipt ?? {
|
|
41
|
-
schemaVersion: 1,
|
|
42
|
-
commandId: "command-1",
|
|
43
|
-
revision: 0,
|
|
44
|
-
status: "pending",
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function effects(log: string[], acknowledge = true): AssignmentSagaEffects {
|
|
50
|
-
return {
|
|
51
|
-
acknowledge: () => {
|
|
52
|
-
log.push("acknowledge");
|
|
53
|
-
return Promise.resolve(acknowledge);
|
|
54
|
-
},
|
|
55
|
-
compensate: () => {
|
|
56
|
-
log.push("compensate");
|
|
57
|
-
return Promise.resolve();
|
|
58
|
-
},
|
|
59
|
-
release: () => {
|
|
60
|
-
log.push("release");
|
|
61
|
-
return Promise.resolve(true);
|
|
62
|
-
},
|
|
63
|
-
rejectCommitted: () => {
|
|
64
|
-
log.push("reject-committed");
|
|
65
|
-
return Promise.resolve();
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
describe("Assignment saga transitions", () => {
|
|
71
|
-
test("orders Replace as claim, commit, acknowledge, release", () => {
|
|
72
|
-
const committed = nextAssignmentPhase(saga("claiming"), "claimed")!;
|
|
73
|
-
const acknowledged = nextAssignmentPhase(committed, "committed")!;
|
|
74
|
-
const releasing = nextAssignmentPhase(acknowledged, "acknowledged")!;
|
|
75
|
-
expect([committed.phase, acknowledged.phase, releasing.phase]).toEqual([
|
|
76
|
-
"committing",
|
|
77
|
-
"acknowledging",
|
|
78
|
-
"releasing",
|
|
79
|
-
]);
|
|
80
|
-
expect(nextAssignmentPhase(releasing, "released")).toBeUndefined();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test("finishes a connection-free Assign after commit", () => {
|
|
84
|
-
expect(
|
|
85
|
-
nextAssignmentPhase(
|
|
86
|
-
saga("committing", {
|
|
87
|
-
operation: "assigning",
|
|
88
|
-
target: {
|
|
89
|
-
assignmentId: "clock",
|
|
90
|
-
packageId: "clock",
|
|
91
|
-
capabilityId: "time",
|
|
92
|
-
},
|
|
93
|
-
previous: undefined,
|
|
94
|
-
previousGeneration: undefined,
|
|
95
|
-
}),
|
|
96
|
-
"committed",
|
|
97
|
-
),
|
|
98
|
-
).toBeUndefined();
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test("strictly decodes durable saga state", () => {
|
|
102
|
-
expect(requireStoredAssignmentSaga(saga("claiming"))).toMatchObject({
|
|
103
|
-
operation: "replacing",
|
|
104
|
-
phase: "claiming",
|
|
105
|
-
});
|
|
106
|
-
expect(() =>
|
|
107
|
-
requireStoredAssignmentSaga({ ...saga("claiming"), extra: true }),
|
|
108
|
-
).toThrow("invalid fields");
|
|
109
|
-
const hidden = saga("claiming") as StoredAssignmentSaga & { hidden?: true };
|
|
110
|
-
Object.defineProperty(hidden, "hidden", { value: true });
|
|
111
|
-
expect(() => requireStoredAssignmentSaga(hidden)).toThrow("invalid fields");
|
|
112
|
-
expect(() =>
|
|
113
|
-
requireStoredAssignmentSaga({
|
|
114
|
-
...saga("claiming"),
|
|
115
|
-
[Symbol("extra")]: true,
|
|
116
|
-
}),
|
|
117
|
-
).toThrow("invalid fields");
|
|
118
|
-
expect(() =>
|
|
119
|
-
requireStoredAssignmentSaga({
|
|
120
|
-
...saga("claiming"),
|
|
121
|
-
acceptedReceipt: {
|
|
122
|
-
schemaVersion: 1,
|
|
123
|
-
commandId: "command-1",
|
|
124
|
-
revision: 0,
|
|
125
|
-
status: "applied",
|
|
126
|
-
},
|
|
127
|
-
}),
|
|
128
|
-
).toThrow("accepted receipt is invalid");
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test("rejects out-of-order advancement", () => {
|
|
132
|
-
expect(() => nextAssignmentPhase(saga("claiming"), "released")).toThrow(
|
|
133
|
-
"cannot apply released while claiming",
|
|
134
|
-
);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("compensates a claiming saga and rejects an unacknowledged commit", async () => {
|
|
138
|
-
const compensated: string[] = [];
|
|
139
|
-
await expect(
|
|
140
|
-
settleAssignmentSaga(saga("claiming"), effects(compensated)),
|
|
141
|
-
).resolves.toBe("compensated");
|
|
142
|
-
expect(compensated).toEqual(["compensate"]);
|
|
143
|
-
|
|
144
|
-
const acknowledged: string[] = [];
|
|
145
|
-
await expect(
|
|
146
|
-
settleAssignmentSaga(saga("acknowledging"), effects(acknowledged)),
|
|
147
|
-
).resolves.toBe("acknowledged");
|
|
148
|
-
expect(acknowledged).toEqual(["acknowledge"]);
|
|
149
|
-
|
|
150
|
-
const log: string[] = [];
|
|
151
|
-
await expect(
|
|
152
|
-
settleAssignmentSaga(saga("acknowledging"), effects(log, false)),
|
|
153
|
-
).resolves.toBe("rejected");
|
|
154
|
-
expect(log).toEqual([
|
|
155
|
-
"acknowledge",
|
|
156
|
-
"compensate",
|
|
157
|
-
"release",
|
|
158
|
-
"reject-committed",
|
|
159
|
-
]);
|
|
160
|
-
});
|
|
161
|
-
});
|
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
decodeConnectionDependencyRequirementV1,
|
|
3
|
-
decodeModelAssignmentV1,
|
|
4
|
-
decodeOperationReceiptV1,
|
|
5
|
-
isPublicIdentifier,
|
|
6
|
-
type CapabilityAssignmentView,
|
|
7
|
-
type ConnectionDependencyRequirementV1,
|
|
8
|
-
type ModelAssignment,
|
|
9
|
-
type OperationReceiptV1,
|
|
10
|
-
} from "@frockbot/configuration-core";
|
|
11
|
-
|
|
12
|
-
export type AssignmentOperationKind = "assigning" | "replacing" | "unassigning";
|
|
13
|
-
|
|
14
|
-
export type AssignmentSagaPhase =
|
|
15
|
-
"claiming" | "committing" | "acknowledging" | "releasing";
|
|
16
|
-
|
|
17
|
-
/** Durable Bot-owned coordinator state, separate from its stable Assignment. */
|
|
18
|
-
export interface StoredAssignmentSaga {
|
|
19
|
-
schemaVersion: 1;
|
|
20
|
-
commandId: string;
|
|
21
|
-
commandFingerprint: string;
|
|
22
|
-
userId: string;
|
|
23
|
-
botId: string;
|
|
24
|
-
operation: AssignmentOperationKind;
|
|
25
|
-
assignmentId: string;
|
|
26
|
-
generation: string;
|
|
27
|
-
phase: AssignmentSagaPhase;
|
|
28
|
-
target?: Omit<CapabilityAssignmentView, "state">;
|
|
29
|
-
claimDispatched?: boolean;
|
|
30
|
-
acknowledgeDispatched?: boolean;
|
|
31
|
-
releaseDispatched?: boolean;
|
|
32
|
-
targetRequirement?: ConnectionDependencyRequirementV1;
|
|
33
|
-
previous?: CapabilityAssignmentView;
|
|
34
|
-
previousGeneration?: string;
|
|
35
|
-
/**
|
|
36
|
-
* The Bot model this Assignment carries. It commits inside the saga's commit
|
|
37
|
-
* phase, so the Connection dependency claim and the model binding are one
|
|
38
|
-
* durable, idempotent unit rather than two commands that can disagree.
|
|
39
|
-
*/
|
|
40
|
-
model?: ModelAssignment;
|
|
41
|
-
/** The Assignment's removal also unbinds the Bot's model. */
|
|
42
|
-
clearModel?: boolean;
|
|
43
|
-
deadlineAt: number;
|
|
44
|
-
acceptedReceipt: OperationReceiptV1;
|
|
45
|
-
receipt?: OperationReceiptV1;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function exact(
|
|
49
|
-
value: Record<PropertyKey, unknown>,
|
|
50
|
-
required: readonly string[],
|
|
51
|
-
optional: readonly string[] = [],
|
|
52
|
-
): void {
|
|
53
|
-
const allowed = new Set<PropertyKey>([...required, ...optional]);
|
|
54
|
-
if (
|
|
55
|
-
!required.every((key) => Object.hasOwn(value, key)) ||
|
|
56
|
-
!Reflect.ownKeys(value).every((key) => allowed.has(key))
|
|
57
|
-
) {
|
|
58
|
-
throw new Error("Stored Assignment saga has invalid fields");
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function identifier(value: unknown, label: string): string {
|
|
63
|
-
if (typeof value !== "string" || !isPublicIdentifier(value)) {
|
|
64
|
-
throw new Error(`Stored Assignment saga ${label} is invalid`);
|
|
65
|
-
}
|
|
66
|
-
return value;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function requireStoredAssignmentSaga(
|
|
70
|
-
input: unknown,
|
|
71
|
-
): StoredAssignmentSaga {
|
|
72
|
-
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
73
|
-
throw new Error("Stored Assignment saga is invalid");
|
|
74
|
-
}
|
|
75
|
-
const value = input as Record<PropertyKey, unknown>;
|
|
76
|
-
exact(
|
|
77
|
-
value,
|
|
78
|
-
[
|
|
79
|
-
"schemaVersion",
|
|
80
|
-
"commandId",
|
|
81
|
-
"commandFingerprint",
|
|
82
|
-
"userId",
|
|
83
|
-
"botId",
|
|
84
|
-
"operation",
|
|
85
|
-
"assignmentId",
|
|
86
|
-
"generation",
|
|
87
|
-
"phase",
|
|
88
|
-
"deadlineAt",
|
|
89
|
-
"acceptedReceipt",
|
|
90
|
-
],
|
|
91
|
-
[
|
|
92
|
-
"target",
|
|
93
|
-
"model",
|
|
94
|
-
"clearModel",
|
|
95
|
-
"targetRequirement",
|
|
96
|
-
"previous",
|
|
97
|
-
"previousGeneration",
|
|
98
|
-
"claimDispatched",
|
|
99
|
-
"acknowledgeDispatched",
|
|
100
|
-
"releaseDispatched",
|
|
101
|
-
"receipt",
|
|
102
|
-
],
|
|
103
|
-
);
|
|
104
|
-
if (
|
|
105
|
-
value.schemaVersion !== 1 ||
|
|
106
|
-
(value.operation !== "assigning" &&
|
|
107
|
-
value.operation !== "replacing" &&
|
|
108
|
-
value.operation !== "unassigning") ||
|
|
109
|
-
(value.phase !== "claiming" &&
|
|
110
|
-
value.phase !== "committing" &&
|
|
111
|
-
value.phase !== "acknowledging" &&
|
|
112
|
-
value.phase !== "releasing") ||
|
|
113
|
-
typeof value.deadlineAt !== "number" ||
|
|
114
|
-
!Number.isFinite(value.deadlineAt)
|
|
115
|
-
) {
|
|
116
|
-
throw new Error("Stored Assignment saga is invalid");
|
|
117
|
-
}
|
|
118
|
-
for (const key of [
|
|
119
|
-
"claimDispatched",
|
|
120
|
-
"acknowledgeDispatched",
|
|
121
|
-
"releaseDispatched",
|
|
122
|
-
"clearModel",
|
|
123
|
-
] as const) {
|
|
124
|
-
if (value[key] !== undefined && typeof value[key] !== "boolean") {
|
|
125
|
-
throw new Error(`Stored Assignment saga ${key} is invalid`);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
const target = (candidate: unknown, state: boolean) => {
|
|
129
|
-
if (
|
|
130
|
-
!candidate ||
|
|
131
|
-
typeof candidate !== "object" ||
|
|
132
|
-
Array.isArray(candidate)
|
|
133
|
-
) {
|
|
134
|
-
throw new Error("Stored Assignment saga target is invalid");
|
|
135
|
-
}
|
|
136
|
-
const item = candidate as Record<PropertyKey, unknown>;
|
|
137
|
-
exact(
|
|
138
|
-
item,
|
|
139
|
-
state
|
|
140
|
-
? ["assignmentId", "packageId", "capabilityId", "state"]
|
|
141
|
-
: ["assignmentId", "packageId", "capabilityId"],
|
|
142
|
-
["connectionId"],
|
|
143
|
-
);
|
|
144
|
-
if (
|
|
145
|
-
state &&
|
|
146
|
-
item.state !== "enabled" &&
|
|
147
|
-
item.state !== "disabled" &&
|
|
148
|
-
item.state !== "unavailable"
|
|
149
|
-
) {
|
|
150
|
-
throw new Error("Stored Assignment saga target state is invalid");
|
|
151
|
-
}
|
|
152
|
-
return {
|
|
153
|
-
assignmentId: identifier(item.assignmentId, "assignmentId"),
|
|
154
|
-
packageId: identifier(item.packageId, "packageId"),
|
|
155
|
-
capabilityId: identifier(item.capabilityId, "capabilityId"),
|
|
156
|
-
connectionId:
|
|
157
|
-
item.connectionId === undefined
|
|
158
|
-
? undefined
|
|
159
|
-
: identifier(item.connectionId, "connectionId"),
|
|
160
|
-
...(state
|
|
161
|
-
? { state: item.state as CapabilityAssignmentView["state"] }
|
|
162
|
-
: {}),
|
|
163
|
-
};
|
|
164
|
-
};
|
|
165
|
-
const acceptedReceipt = decodeOperationReceiptV1(value.acceptedReceipt);
|
|
166
|
-
if (
|
|
167
|
-
acceptedReceipt.status !== "pending" ||
|
|
168
|
-
acceptedReceipt.commandId !== value.commandId
|
|
169
|
-
) {
|
|
170
|
-
throw new Error("Stored Assignment saga accepted receipt is invalid");
|
|
171
|
-
}
|
|
172
|
-
return {
|
|
173
|
-
schemaVersion: 1,
|
|
174
|
-
commandId: identifier(value.commandId, "commandId"),
|
|
175
|
-
commandFingerprint:
|
|
176
|
-
typeof value.commandFingerprint === "string" &&
|
|
177
|
-
value.commandFingerprint.length <= 2_000
|
|
178
|
-
? value.commandFingerprint
|
|
179
|
-
: (() => {
|
|
180
|
-
throw new Error("Stored Assignment saga fingerprint is invalid");
|
|
181
|
-
})(),
|
|
182
|
-
userId: identifier(value.userId, "userId"),
|
|
183
|
-
botId: identifier(value.botId, "botId"),
|
|
184
|
-
operation: value.operation,
|
|
185
|
-
assignmentId: identifier(value.assignmentId, "assignmentId"),
|
|
186
|
-
generation: identifier(value.generation, "generation"),
|
|
187
|
-
phase: value.phase,
|
|
188
|
-
target:
|
|
189
|
-
value.target === undefined
|
|
190
|
-
? undefined
|
|
191
|
-
: (target(value.target, false) as Omit<
|
|
192
|
-
CapabilityAssignmentView,
|
|
193
|
-
"state"
|
|
194
|
-
>),
|
|
195
|
-
model:
|
|
196
|
-
value.model === undefined
|
|
197
|
-
? undefined
|
|
198
|
-
: decodeModelAssignmentV1(value.model),
|
|
199
|
-
clearModel: value.clearModel as boolean | undefined,
|
|
200
|
-
targetRequirement:
|
|
201
|
-
value.targetRequirement === undefined
|
|
202
|
-
? undefined
|
|
203
|
-
: decodeConnectionDependencyRequirementV1(value.targetRequirement),
|
|
204
|
-
previous:
|
|
205
|
-
value.previous === undefined
|
|
206
|
-
? undefined
|
|
207
|
-
: (target(value.previous, true) as CapabilityAssignmentView),
|
|
208
|
-
previousGeneration:
|
|
209
|
-
value.previousGeneration === undefined
|
|
210
|
-
? undefined
|
|
211
|
-
: identifier(value.previousGeneration, "previousGeneration"),
|
|
212
|
-
claimDispatched: value.claimDispatched as boolean | undefined,
|
|
213
|
-
acknowledgeDispatched: value.acknowledgeDispatched as boolean | undefined,
|
|
214
|
-
releaseDispatched: value.releaseDispatched as boolean | undefined,
|
|
215
|
-
deadlineAt: value.deadlineAt,
|
|
216
|
-
acceptedReceipt,
|
|
217
|
-
receipt:
|
|
218
|
-
value.receipt === undefined
|
|
219
|
-
? undefined
|
|
220
|
-
: decodeOperationReceiptV1(value.receipt),
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
export function nextAssignmentPhase(
|
|
225
|
-
saga: StoredAssignmentSaga,
|
|
226
|
-
event: "claimed" | "committed" | "acknowledged" | "released",
|
|
227
|
-
): StoredAssignmentSaga | undefined {
|
|
228
|
-
if (saga.phase === "claiming" && event === "claimed") {
|
|
229
|
-
return { ...saga, phase: "committing" };
|
|
230
|
-
}
|
|
231
|
-
if (saga.phase === "committing" && event === "committed") {
|
|
232
|
-
if (saga.target?.connectionId) {
|
|
233
|
-
return { ...saga, phase: "acknowledging" };
|
|
234
|
-
}
|
|
235
|
-
if (saga.previous?.connectionId) {
|
|
236
|
-
return { ...saga, phase: "releasing" };
|
|
237
|
-
}
|
|
238
|
-
return undefined;
|
|
239
|
-
}
|
|
240
|
-
if (saga.phase === "acknowledging" && event === "acknowledged") {
|
|
241
|
-
return saga.previous?.connectionId
|
|
242
|
-
? { ...saga, phase: "releasing" }
|
|
243
|
-
: undefined;
|
|
244
|
-
}
|
|
245
|
-
if (saga.phase === "releasing" && event === "released") return undefined;
|
|
246
|
-
throw new Error(`Assignment saga cannot apply ${event} while ${saga.phase}`);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
export type AssignmentSagaSettlement =
|
|
250
|
-
"acknowledged" | "compensated" | "rejected";
|
|
251
|
-
|
|
252
|
-
export interface AssignmentSagaEffects {
|
|
253
|
-
acknowledge(saga: StoredAssignmentSaga): Promise<boolean>;
|
|
254
|
-
compensate(saga: StoredAssignmentSaga): Promise<void>;
|
|
255
|
-
release(saga: StoredAssignmentSaga): Promise<boolean>;
|
|
256
|
-
rejectCommitted(saga: StoredAssignmentSaga): Promise<void>;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
export async function settleAssignmentSaga(
|
|
260
|
-
saga: StoredAssignmentSaga,
|
|
261
|
-
effects: AssignmentSagaEffects,
|
|
262
|
-
): Promise<AssignmentSagaSettlement> {
|
|
263
|
-
if (saga.phase === "claiming") {
|
|
264
|
-
await effects.compensate(saga);
|
|
265
|
-
return "compensated";
|
|
266
|
-
}
|
|
267
|
-
if (await effects.acknowledge(saga)) return "acknowledged";
|
|
268
|
-
await effects.compensate(saga);
|
|
269
|
-
if (!(await effects.release(saga))) {
|
|
270
|
-
throw new Error("Rejected Connection dependency was not released");
|
|
271
|
-
}
|
|
272
|
-
await effects.rejectCommitted(saga);
|
|
273
|
-
return "rejected";
|
|
274
|
-
}
|