@axiom-lattice/protocols 4.1.1 → 4.1.2
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +6 -0
- package/dist/index.d.mts +717 -18
- package/dist/index.d.ts +717 -18
- package/dist/index.js +409 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +388 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/BindingProtocol.ts +87 -11
- package/src/ChannelInstallationStoreProtocol.ts +23 -4
- package/src/ExactDataSnapshot.ts +119 -0
- package/src/ProjectBotMembershipStoreProtocol.ts +48 -0
- package/src/ProjectMembershipStoreProtocol.ts +58 -0
- package/src/ProjectRoomMessageStoreProtocol.ts +26 -0
- package/src/ProjectRoomProtocol.ts +143 -0
- package/src/ProjectRoomRealtimeProtocol.ts +349 -0
- package/src/ProjectRoomStoreProtocol.ts +16 -0
- package/src/TaskStoreProtocol.ts +66 -2
- package/src/TaskWorkItemProtocol.ts +86 -0
- package/src/TrustedRunContextProtocol.ts +119 -0
- package/src/__tests__/BindingProtocol.test.ts +36 -0
- package/src/__tests__/ExactDataSnapshot.test.ts +105 -0
- package/src/__tests__/ProjectRoomProtocol.test.ts +48 -0
- package/src/__tests__/ProjectRoomRealtimeProtocol.test.ts +185 -0
- package/src/__tests__/ProjectRoomStores.test.ts +363 -0
- package/src/__tests__/ProjectTaskProtocol.test.ts +29 -0
- package/src/__tests__/TaskWorkItemProtocol.test.ts +42 -0
- package/src/__tests__/TrustedRunContextProtocol.test.ts +265 -0
- package/src/index.ts +8 -0
- package/type-tests/task-work-item-store-compatibility.ts +14 -2
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ProjectBotMembership,
|
|
3
|
+
ProjectBotMembershipStore,
|
|
4
|
+
ProjectMembership,
|
|
5
|
+
ProjectMembershipStore,
|
|
6
|
+
ProjectRoom,
|
|
7
|
+
ProjectRoomMessage,
|
|
8
|
+
ProjectRoomMessageCursor,
|
|
9
|
+
ProjectRoomMessageStore,
|
|
10
|
+
ProjectRoomStore,
|
|
11
|
+
} from "../index";
|
|
12
|
+
|
|
13
|
+
type RoomInput = Parameters<ProjectRoomStore["ensureMainRoom"]>[0];
|
|
14
|
+
type RoomLookupResult = Awaited<ReturnType<ProjectRoomStore["getMainRoom"]>>;
|
|
15
|
+
type MembershipInput = Parameters<ProjectMembershipStore["create"]>[0];
|
|
16
|
+
type InitialOwnerInput = Parameters<ProjectMembershipStore["createInitialOwner"]>[0];
|
|
17
|
+
type InitialOwnerResult = Awaited<ReturnType<ProjectMembershipStore["createInitialOwner"]>>;
|
|
18
|
+
type RoleInput = Parameters<ProjectMembershipStore["updateRole"]>[0];
|
|
19
|
+
type RoleResult = Awaited<ReturnType<ProjectMembershipStore["updateRole"]>>;
|
|
20
|
+
type RemoveInput = Parameters<ProjectMembershipStore["remove"]>[0];
|
|
21
|
+
type RemoveResult = Awaited<ReturnType<ProjectMembershipStore["remove"]>>;
|
|
22
|
+
type BotInput = Parameters<ProjectBotMembershipStore["save"]>[0];
|
|
23
|
+
type BotSaveResult = Awaited<ReturnType<ProjectBotMembershipStore["save"]>>;
|
|
24
|
+
type BotUpdateInput = Parameters<ProjectBotMembershipStore["update"]>[0];
|
|
25
|
+
type BotUpdateResult = Awaited<ReturnType<ProjectBotMembershipStore["update"]>>;
|
|
26
|
+
type MessageInput = Parameters<ProjectRoomMessageStore["create"]>[0];
|
|
27
|
+
type IdempotentMessageInput = Parameters<ProjectRoomMessageStore["createIdempotent"]>[0];
|
|
28
|
+
type MessageListInput = Parameters<ProjectRoomMessageStore["list"]>[0];
|
|
29
|
+
|
|
30
|
+
const room: ProjectRoom = {
|
|
31
|
+
id: "room-1",
|
|
32
|
+
tenantId: "tenant-1",
|
|
33
|
+
workspaceId: "workspace-1",
|
|
34
|
+
projectId: "project-1",
|
|
35
|
+
type: "main",
|
|
36
|
+
name: "Main room",
|
|
37
|
+
createdAt: new Date(0),
|
|
38
|
+
updatedAt: new Date(0),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const membership: ProjectMembership = {
|
|
42
|
+
id: "membership-1",
|
|
43
|
+
tenantId: "tenant-1",
|
|
44
|
+
projectId: "project-1",
|
|
45
|
+
userId: "user-1",
|
|
46
|
+
role: "owner",
|
|
47
|
+
status: "active",
|
|
48
|
+
joinedAt: new Date(0),
|
|
49
|
+
updatedAt: new Date(0),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const botMembership: ProjectBotMembership = {
|
|
53
|
+
id: "bot-membership-1",
|
|
54
|
+
tenantId: "tenant-1",
|
|
55
|
+
workspaceId: "workspace-1",
|
|
56
|
+
projectId: "project-1",
|
|
57
|
+
roomId: "room-1",
|
|
58
|
+
assistantId: "assistant-1",
|
|
59
|
+
role: "coordinator",
|
|
60
|
+
title: "Coordinator",
|
|
61
|
+
mentionName: "coordinator",
|
|
62
|
+
status: "active",
|
|
63
|
+
roomThreadId: "thread-1",
|
|
64
|
+
joinedAt: new Date(0),
|
|
65
|
+
updatedAt: new Date(0),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const message: ProjectRoomMessage = {
|
|
69
|
+
id: "message-1",
|
|
70
|
+
tenantId: "tenant-1",
|
|
71
|
+
workspaceId: "workspace-1",
|
|
72
|
+
projectId: "project-1",
|
|
73
|
+
roomId: "room-1",
|
|
74
|
+
author: { type: "system" },
|
|
75
|
+
content: { type: "text", text: "Room ready" },
|
|
76
|
+
mentions: [],
|
|
77
|
+
source: "system",
|
|
78
|
+
createdAt: new Date(0),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
function assertNever(value: never): never {
|
|
82
|
+
throw new Error(`Unexpected protocol result: ${String(value)}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function expectInitialOwnerResult(result: InitialOwnerResult, expected: InitialOwnerResult["kind"]): void {
|
|
86
|
+
switch (result.kind) {
|
|
87
|
+
case "created":
|
|
88
|
+
case "existing":
|
|
89
|
+
expect(result.kind).toBe(expected);
|
|
90
|
+
expect(result.membership).toEqual(expect.objectContaining({ projectId: "project-1" }));
|
|
91
|
+
return;
|
|
92
|
+
case "already_initialized":
|
|
93
|
+
expect(result.kind).toBe(expected);
|
|
94
|
+
return;
|
|
95
|
+
default:
|
|
96
|
+
assertNever(result);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function expectRoleResult(result: RoleResult, expected: RoleResult["kind"]): void {
|
|
101
|
+
switch (result.kind) {
|
|
102
|
+
case "updated":
|
|
103
|
+
expect(result.kind).toBe(expected);
|
|
104
|
+
expect(result.membership).toEqual(expect.objectContaining({ id: "membership-1" }));
|
|
105
|
+
return;
|
|
106
|
+
case "removed":
|
|
107
|
+
expect(result.kind).toBe(expected);
|
|
108
|
+
return;
|
|
109
|
+
case "not_found":
|
|
110
|
+
case "conflict":
|
|
111
|
+
case "last_owner":
|
|
112
|
+
expect(result.kind).toBe(expected);
|
|
113
|
+
return;
|
|
114
|
+
default:
|
|
115
|
+
assertNever(result);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function expectRemoveResult(result: RemoveResult, expected: RemoveResult["kind"]): void {
|
|
120
|
+
switch (result.kind) {
|
|
121
|
+
case "updated":
|
|
122
|
+
expect(result.kind).toBe(expected);
|
|
123
|
+
return;
|
|
124
|
+
case "removed":
|
|
125
|
+
expect(result.kind).toBe(expected);
|
|
126
|
+
expect(result.membership).toEqual(expect.objectContaining({ id: "membership-1", status: "removed" }));
|
|
127
|
+
return;
|
|
128
|
+
case "not_found":
|
|
129
|
+
case "conflict":
|
|
130
|
+
case "last_owner":
|
|
131
|
+
expect(result.kind).toBe(expected);
|
|
132
|
+
return;
|
|
133
|
+
default:
|
|
134
|
+
assertNever(result);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function expectBotSaveResult(result: BotSaveResult, expected: BotSaveResult["kind"]): void {
|
|
139
|
+
switch (result.kind) {
|
|
140
|
+
case "created":
|
|
141
|
+
case "updated":
|
|
142
|
+
case "reactivated":
|
|
143
|
+
expect(result.kind).toBe(expected);
|
|
144
|
+
expect(result.membership).toEqual(expect.objectContaining({ projectId: "project-1" }));
|
|
145
|
+
return;
|
|
146
|
+
case "coordinator_conflict":
|
|
147
|
+
case "mention_conflict":
|
|
148
|
+
expect(result.kind).toBe(expected);
|
|
149
|
+
return;
|
|
150
|
+
default:
|
|
151
|
+
assertNever(result);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function expectBotUpdateResult(result: BotUpdateResult, expected: BotUpdateResult["kind"]): void {
|
|
156
|
+
switch (result.kind) {
|
|
157
|
+
case "updated":
|
|
158
|
+
expect(result.kind).toBe(expected);
|
|
159
|
+
expect(result.membership).toEqual(expect.objectContaining({ id: "bot-membership-1" }));
|
|
160
|
+
return;
|
|
161
|
+
case "not_found":
|
|
162
|
+
case "conflict":
|
|
163
|
+
case "coordinator_conflict":
|
|
164
|
+
case "mention_conflict":
|
|
165
|
+
expect(result.kind).toBe(expected);
|
|
166
|
+
return;
|
|
167
|
+
default:
|
|
168
|
+
assertNever(result);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
class FakeProjectRoomStore implements ProjectRoomStore {
|
|
173
|
+
async ensureMainRoom(input: RoomInput): Promise<Awaited<ReturnType<ProjectRoomStore["ensureMainRoom"]>>> {
|
|
174
|
+
return { ...room, ...input };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async getMainRoom(...args: Parameters<ProjectRoomStore["getMainRoom"]>): Promise<RoomLookupResult> {
|
|
178
|
+
const [tenantId, projectId] = args;
|
|
179
|
+
return tenantId === "tenant-1" && projectId === "project-1" ? room : null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
class FakeProjectMembershipStore implements ProjectMembershipStore {
|
|
184
|
+
async list(...args: Parameters<ProjectMembershipStore["list"]>): Promise<Awaited<ReturnType<ProjectMembershipStore["list"]>>> {
|
|
185
|
+
const [tenantId, projectId] = args;
|
|
186
|
+
return tenantId === "tenant-1" && projectId === "project-1" ? [membership] : [];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async findByUser(...args: Parameters<ProjectMembershipStore["findByUser"]>): Promise<Awaited<ReturnType<ProjectMembershipStore["findByUser"]>>> {
|
|
190
|
+
const [tenantId, projectId, userId] = args;
|
|
191
|
+
return tenantId === "tenant-1" && projectId === "project-1" && userId === "user-1" ? membership : null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async create(input: MembershipInput): Promise<Awaited<ReturnType<ProjectMembershipStore["create"]>>> {
|
|
195
|
+
return { ...input, joinedAt: new Date(0), updatedAt: new Date(0) };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async createInitialOwner(input: InitialOwnerInput): Promise<InitialOwnerResult> {
|
|
199
|
+
if (input.id === "already-initialized") return { kind: "already_initialized" };
|
|
200
|
+
return { kind: input.id === "existing" ? "existing" : "created", membership: { ...membership, ...input } };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async updateRole(input: RoleInput): Promise<RoleResult> {
|
|
204
|
+
if (input.id === "not-found") return { kind: "not_found" };
|
|
205
|
+
if (input.id === "conflict") return { kind: "conflict" };
|
|
206
|
+
if (input.id === "last-owner") return { kind: "last_owner" };
|
|
207
|
+
return { kind: "updated", membership };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async remove(input: RemoveInput): Promise<RemoveResult> {
|
|
211
|
+
if (input.id === "not-found") return { kind: "not_found" };
|
|
212
|
+
if (input.id === "conflict") return { kind: "conflict" };
|
|
213
|
+
if (input.id === "last-owner") return { kind: "last_owner" };
|
|
214
|
+
return { kind: "removed", membership: { ...membership, status: "removed" } };
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
class FakeProjectBotMembershipStore implements ProjectBotMembershipStore {
|
|
219
|
+
async list(...args: Parameters<ProjectBotMembershipStore["list"]>): Promise<Awaited<ReturnType<ProjectBotMembershipStore["list"]>>> {
|
|
220
|
+
const [tenantId, projectId] = args;
|
|
221
|
+
return tenantId === "tenant-1" && projectId === "project-1" ? [botMembership] : [];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async findById(...args: Parameters<ProjectBotMembershipStore["findById"]>): Promise<Awaited<ReturnType<ProjectBotMembershipStore["findById"]>>> {
|
|
225
|
+
const [tenantId, id] = args;
|
|
226
|
+
return tenantId === "tenant-1" && id === "bot-membership-1" ? botMembership : null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async findByAssistant(...args: Parameters<ProjectBotMembershipStore["findByAssistant"]>): Promise<Awaited<ReturnType<ProjectBotMembershipStore["findByAssistant"]>>> {
|
|
230
|
+
const [tenantId, projectId, assistantId] = args;
|
|
231
|
+
return tenantId === "tenant-1" && projectId === "project-1" && assistantId === "assistant-1" ? botMembership : null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async save(input: BotInput): Promise<BotSaveResult> {
|
|
235
|
+
if (input.id === "coordinator-conflict") return { kind: "coordinator_conflict" };
|
|
236
|
+
if (input.id === "mention-conflict") return { kind: "mention_conflict" };
|
|
237
|
+
const kind = input.id === "updated" ? "updated" : input.id === "reactivated" ? "reactivated" : "created";
|
|
238
|
+
return { kind, membership: { ...input, joinedAt: new Date(0), updatedAt: new Date(0) } };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async update(input: BotUpdateInput): Promise<BotUpdateResult> {
|
|
242
|
+
if (input.id === "not-found") return { kind: "not_found" };
|
|
243
|
+
if (input.id === "conflict") return { kind: "conflict" };
|
|
244
|
+
if (input.id === "coordinator-conflict") return { kind: "coordinator_conflict" };
|
|
245
|
+
if (input.id === "mention-conflict") return { kind: "mention_conflict" };
|
|
246
|
+
return { kind: "updated", membership: { ...botMembership, ...input.patch } };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
class FakeProjectRoomMessageStore implements ProjectRoomMessageStore {
|
|
251
|
+
async create(input: MessageInput): Promise<Awaited<ReturnType<ProjectRoomMessageStore["create"]>>> {
|
|
252
|
+
return { ...input, createdAt: new Date(0) };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async createIdempotent(input: IdempotentMessageInput): Promise<Awaited<ReturnType<ProjectRoomMessageStore["createIdempotent"]>>> {
|
|
256
|
+
return { ...input, createdAt: new Date(0) };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async list(input: MessageListInput): Promise<Awaited<ReturnType<ProjectRoomMessageStore["list"]>>> {
|
|
260
|
+
return input.tenantId === "tenant-1" && input.roomId === "room-1" && input.limit > 0 ? [message] : [];
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async findById(...args: Parameters<ProjectRoomMessageStore["findById"]>): Promise<Awaited<ReturnType<ProjectRoomMessageStore["findById"]>>> {
|
|
264
|
+
const [tenantId, id] = args;
|
|
265
|
+
return tenantId === "tenant-1" && id === "message-1" ? message : null;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
describe("ProjectRoomStore", () => {
|
|
270
|
+
const store: ProjectRoomStore = new FakeProjectRoomStore();
|
|
271
|
+
|
|
272
|
+
it("creates and looks up the main room, including a missing room", async () => {
|
|
273
|
+
expect(await store.ensureMainRoom(room)).toEqual(room);
|
|
274
|
+
expect(await store.getMainRoom("tenant-1", "project-1")).toEqual(room);
|
|
275
|
+
expect(await store.getMainRoom("tenant-1", "missing-project")).toBeNull();
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
describe("ProjectMembershipStore", () => {
|
|
280
|
+
const store: ProjectMembershipStore = new FakeProjectMembershipStore();
|
|
281
|
+
|
|
282
|
+
it("lists, creates, and returns nullable user membership lookups", async () => {
|
|
283
|
+
expect(await store.list("tenant-1", "project-1")).toEqual([membership]);
|
|
284
|
+
expect(await store.findByUser("tenant-1", "project-1", "user-1")).toEqual(membership);
|
|
285
|
+
expect(await store.findByUser("tenant-1", "project-1", "missing-user")).toBeNull();
|
|
286
|
+
expect(await store.create(membership)).toEqual(membership);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("distinguishes initial-owner outcomes", async () => {
|
|
290
|
+
expectInitialOwnerResult(await store.createInitialOwner(membership), "created");
|
|
291
|
+
expectInitialOwnerResult(await store.createInitialOwner({ ...membership, id: "existing" }), "existing");
|
|
292
|
+
expectInitialOwnerResult(await store.createInitialOwner({ ...membership, id: "already-initialized" }), "already_initialized");
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("returns every role update result", async () => {
|
|
296
|
+
const cases: ReadonlyArray<readonly [string, RoleResult["kind"]]> = [
|
|
297
|
+
["membership-1", "updated"], ["not-found", "not_found"], ["conflict", "conflict"], ["last-owner", "last_owner"],
|
|
298
|
+
];
|
|
299
|
+
for (const [id, expected] of cases) {
|
|
300
|
+
expectRoleResult(await store.updateRole({ tenantId: "tenant-1", id, role: "admin", expectedUpdatedAt: new Date(0) }), expected);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("returns every membership removal result", async () => {
|
|
305
|
+
const cases: ReadonlyArray<readonly [string, RemoveResult["kind"]]> = [
|
|
306
|
+
["membership-1", "removed"], ["not-found", "not_found"], ["conflict", "conflict"], ["last-owner", "last_owner"],
|
|
307
|
+
];
|
|
308
|
+
for (const [id, expected] of cases) {
|
|
309
|
+
expectRemoveResult(await store.remove({ tenantId: "tenant-1", id, expectedUpdatedAt: new Date(0) }), expected);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
describe("ProjectBotMembershipStore", () => {
|
|
315
|
+
const store: ProjectBotMembershipStore = new FakeProjectBotMembershipStore();
|
|
316
|
+
|
|
317
|
+
it("lists memberships and covers nullable bot lookups", async () => {
|
|
318
|
+
expect(await store.list("tenant-1", "project-1")).toEqual([botMembership]);
|
|
319
|
+
expect(await store.findById("tenant-1", "bot-membership-1")).toEqual(botMembership);
|
|
320
|
+
expect(await store.findById("tenant-1", "missing-bot")).toBeNull();
|
|
321
|
+
expect(await store.findByAssistant("tenant-1", "project-1", "assistant-1")).toEqual(botMembership);
|
|
322
|
+
expect(await store.findByAssistant("tenant-1", "project-1", "missing-assistant")).toBeNull();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("returns every save result", async () => {
|
|
326
|
+
const cases: ReadonlyArray<readonly [string, BotSaveResult["kind"]]> = [
|
|
327
|
+
["bot-membership-1", "created"],
|
|
328
|
+
["updated", "updated"],
|
|
329
|
+
["reactivated", "reactivated"],
|
|
330
|
+
["coordinator-conflict", "coordinator_conflict"],
|
|
331
|
+
["mention-conflict", "mention_conflict"],
|
|
332
|
+
];
|
|
333
|
+
for (const [id, expected] of cases) {
|
|
334
|
+
expectBotSaveResult(await store.save({ ...botMembership, id }), expected);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it("returns every update result", async () => {
|
|
339
|
+
const cases: ReadonlyArray<readonly [string, BotUpdateResult["kind"]]> = [
|
|
340
|
+
["bot-membership-1", "updated"],
|
|
341
|
+
["not-found", "not_found"],
|
|
342
|
+
["conflict", "conflict"],
|
|
343
|
+
["coordinator-conflict", "coordinator_conflict"],
|
|
344
|
+
["mention-conflict", "mention_conflict"],
|
|
345
|
+
];
|
|
346
|
+
for (const [id, expected] of cases) {
|
|
347
|
+
expectBotUpdateResult(await store.update({ tenantId: "tenant-1", id, patch: { title: "Lead" }, expectedUpdatedAt: new Date(0) }), expected);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
describe("ProjectRoomMessageStore", () => {
|
|
353
|
+
const store: ProjectRoomMessageStore = new FakeProjectRoomMessageStore();
|
|
354
|
+
|
|
355
|
+
it("creates, idempotently creates, pages, and looks up messages", async () => {
|
|
356
|
+
const cursor: ProjectRoomMessageCursor = { createdAt: message.createdAt, id: message.id };
|
|
357
|
+
expect(await store.create(message)).toEqual(message);
|
|
358
|
+
expect(await store.createIdempotent({ ...message, idempotencyKey: "key-1" })).toEqual({ ...message, idempotencyKey: "key-1" });
|
|
359
|
+
expect(await store.list({ tenantId: "tenant-1", roomId: "room-1", before: cursor, limit: 10 })).toEqual([message]);
|
|
360
|
+
expect(await store.findById("tenant-1", "message-1")).toEqual(message);
|
|
361
|
+
expect(await store.findById("tenant-1", "missing-message")).toBeNull();
|
|
362
|
+
});
|
|
363
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ProjectTaskThreadMetadata } from "../ProjectRoomProtocol";
|
|
2
|
+
|
|
3
|
+
describe("Project Task protocol types", () => {
|
|
4
|
+
it("models task execution thread metadata", () => {
|
|
5
|
+
const metadata: ProjectTaskThreadMetadata = {
|
|
6
|
+
source: "project_task",
|
|
7
|
+
kind: "task_execution",
|
|
8
|
+
workspaceId: "workspace-1",
|
|
9
|
+
projectId: "project-1",
|
|
10
|
+
roomId: "room-1",
|
|
11
|
+
membershipId: "member-1",
|
|
12
|
+
assistantId: "agent-1",
|
|
13
|
+
taskId: "task-1",
|
|
14
|
+
parentTaskId: "parent-1",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
expect(metadata).toEqual({
|
|
18
|
+
source: "project_task",
|
|
19
|
+
kind: "task_execution",
|
|
20
|
+
workspaceId: "workspace-1",
|
|
21
|
+
projectId: "project-1",
|
|
22
|
+
roomId: "room-1",
|
|
23
|
+
membershipId: "member-1",
|
|
24
|
+
assistantId: "agent-1",
|
|
25
|
+
taskId: "task-1",
|
|
26
|
+
parentTaskId: "parent-1",
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
EXECUTION_RESULT_EVENT_KEY_PATTERN_SOURCE,
|
|
3
|
+
MAX_PENDING_EXECUTION_RESULTS_LIMIT,
|
|
4
|
+
PROJECT_TASK_LIFECYCLE_ACTIONS,
|
|
5
|
+
ProjectTaskStoreUnsupportedError,
|
|
3
6
|
isExecutionResultEventKey,
|
|
7
|
+
requireProjectTaskWorkItemStore,
|
|
4
8
|
} from "../TaskWorkItemProtocol";
|
|
5
9
|
import { readFileSync } from "node:fs";
|
|
6
10
|
import type {
|
|
@@ -44,6 +48,44 @@ describe("execution result event keys", () => {
|
|
|
44
48
|
expect(legacyStore).toBeInstanceOf(LegacyTaskWorkItemStore);
|
|
45
49
|
});
|
|
46
50
|
|
|
51
|
+
it("coexists with Project Task lifecycle protocol constants", () => {
|
|
52
|
+
expect(PROJECT_TASK_LIFECYCLE_ACTIONS).toEqual([
|
|
53
|
+
"in_progress",
|
|
54
|
+
"interrupted",
|
|
55
|
+
"failed",
|
|
56
|
+
"completed",
|
|
57
|
+
"cancelled",
|
|
58
|
+
"reassigned",
|
|
59
|
+
]);
|
|
60
|
+
expect(isExecutionResultEventKey("execution-result:thread:1")).toBe(true);
|
|
61
|
+
expect(MAX_PENDING_EXECUTION_RESULTS_LIMIT).toBe(1_000);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("reports all missing Project Task store methods", () => {
|
|
65
|
+
expect(() => requireProjectTaskWorkItemStore(legacyStore)).toThrow(
|
|
66
|
+
expect.objectContaining({
|
|
67
|
+
code: "PROJECT_TASK_STORE_UNSUPPORTED",
|
|
68
|
+
missingMethods: ["createIfTaskSnapshot", "listProjectLifecycleEvents"],
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
requireProjectTaskWorkItemStore(legacyStore);
|
|
74
|
+
throw new Error("Expected Project Task store refinement to fail");
|
|
75
|
+
} catch (error) {
|
|
76
|
+
expect(error).toBeInstanceOf(ProjectTaskStoreUnsupportedError);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("returns a complete Project Task store unchanged", () => {
|
|
81
|
+
const projectStore = Object.assign(new LegacyTaskWorkItemStore(), {
|
|
82
|
+
createIfTaskSnapshot: async () => null,
|
|
83
|
+
listProjectLifecycleEvents: async () => [],
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(requireProjectTaskWorkItemStore(projectStore)).toBe(projectStore);
|
|
87
|
+
});
|
|
88
|
+
|
|
47
89
|
it.each([
|
|
48
90
|
"execution-result:550e8400-e29b-41d4-a716-446655440000",
|
|
49
91
|
"execution-result:thread.segment_1:attempt-2",
|