@assistant-ui/core 0.2.9 → 0.2.10
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/dist/model-context/tool.d.ts +1 -1
- package/dist/model-context/tool.js +1 -1
- package/dist/model-context/tool.js.map +1 -1
- package/dist/model-context/types.js +17 -2
- package/dist/model-context/types.js.map +1 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.js +12 -2
- package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
- package/dist/react/client/Tools.d.ts.map +1 -1
- package/dist/react/client/Tools.js +9 -3
- package/dist/react/client/Tools.js.map +1 -1
- package/dist/react/index.d.ts +7 -3
- package/dist/react/index.js +6 -2
- package/dist/react/model-context/define-mcp-toolkit.d.ts +12 -0
- package/dist/react/model-context/define-mcp-toolkit.d.ts.map +1 -0
- package/dist/react/model-context/define-mcp-toolkit.js +14 -0
- package/dist/react/model-context/define-mcp-toolkit.js.map +1 -0
- package/dist/react/model-context/define-toolkit.d.ts +4 -3
- package/dist/react/model-context/define-toolkit.d.ts.map +1 -1
- package/dist/react/model-context/define-toolkit.js +1 -14
- package/dist/react/model-context/define-toolkit.js.map +1 -1
- package/dist/react/model-context/hitl.d.ts +8 -4
- package/dist/react/model-context/hitl.d.ts.map +1 -1
- package/dist/react/model-context/hitl.js +9 -5
- package/dist/react/model-context/hitl.js.map +1 -1
- package/dist/react/model-context/makeAssistantTool.d.ts +8 -0
- package/dist/react/model-context/makeAssistantTool.d.ts.map +1 -1
- package/dist/react/model-context/makeAssistantTool.js +4 -0
- package/dist/react/model-context/makeAssistantTool.js.map +1 -1
- package/dist/react/model-context/makeAssistantToolUI.d.ts +8 -0
- package/dist/react/model-context/makeAssistantToolUI.d.ts.map +1 -1
- package/dist/react/model-context/makeAssistantToolUI.js +4 -0
- package/dist/react/model-context/makeAssistantToolUI.js.map +1 -1
- package/dist/react/model-context/provider-tool.d.ts +15 -0
- package/dist/react/model-context/provider-tool.d.ts.map +1 -0
- package/dist/react/model-context/provider-tool.js +12 -0
- package/dist/react/model-context/provider-tool.js.map +1 -0
- package/dist/react/model-context/stub-tool.d.ts +12 -0
- package/dist/react/model-context/stub-tool.d.ts.map +1 -0
- package/dist/react/model-context/stub-tool.js +15 -0
- package/dist/react/model-context/stub-tool.js.map +1 -0
- package/dist/react/model-context/toolbox.d.ts +62 -15
- package/dist/react/model-context/toolbox.d.ts.map +1 -1
- package/dist/react/model-context/toolbox.js +19 -1
- package/dist/react/model-context/toolbox.js.map +1 -1
- package/dist/react/model-context/useAssistantTool.d.ts +11 -1
- package/dist/react/model-context/useAssistantTool.d.ts.map +1 -1
- package/dist/react/model-context/useAssistantTool.js +12 -6
- package/dist/react/model-context/useAssistantTool.js.map +1 -1
- package/dist/react/model-context/useAssistantToolUI.d.ts +13 -4
- package/dist/react/model-context/useAssistantToolUI.d.ts.map +1 -1
- package/dist/react/model-context/useAssistantToolUI.js +6 -3
- package/dist/react/model-context/useAssistantToolUI.js.map +1 -1
- package/dist/react/model-context/useAuiToolOverrides.d.ts +22 -0
- package/dist/react/model-context/useAuiToolOverrides.d.ts.map +1 -0
- package/dist/react/model-context/useAuiToolOverrides.js +31 -0
- package/dist/react/model-context/useAuiToolOverrides.js.map +1 -0
- package/dist/react/primitives/part/PartMessages.d.ts +13 -11
- package/dist/react/primitives/part/PartMessages.d.ts.map +1 -1
- package/dist/react/primitives/part/PartMessages.js +13 -11
- package/dist/react/primitives/part/PartMessages.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +1 -0
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +28 -0
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +9 -2
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
- package/dist/runtime/api/thread-list-item-runtime.d.ts +2 -0
- package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-list-item-runtime.js +6 -0
- package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
- package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +1 -0
- package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-adapter.d.ts +2 -0
- package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +1 -0
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +5 -0
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
- package/dist/runtimes/remote-thread-list/adapter/in-memory.d.ts +1 -0
- package/dist/runtimes/remote-thread-list/adapter/in-memory.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/adapter/in-memory.js +3 -0
- package/dist/runtimes/remote-thread-list/adapter/in-memory.js.map +1 -1
- package/dist/runtimes/remote-thread-list/types.d.ts +1 -0
- package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-list-item-runtime-client.js +1 -0
- package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
- package/dist/store/scopes/thread-list-item.d.ts +1 -0
- package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/model-context/tool.ts +1 -1
- package/src/model-context/types.ts +21 -3
- package/src/react/adapters/LocalStorageThreadListAdapter.tsx +15 -2
- package/src/react/client/Tools.ts +22 -7
- package/src/react/index.ts +14 -3
- package/src/react/model-context/define-mcp-toolkit.ts +16 -0
- package/src/react/model-context/define-toolkit.test.ts +92 -4
- package/src/react/model-context/define-toolkit.ts +21 -3
- package/src/react/model-context/hitl.ts +10 -5
- package/src/react/model-context/makeAssistantTool.ts +8 -0
- package/src/react/model-context/makeAssistantToolUI.ts +8 -0
- package/src/react/model-context/provider-tool.ts +30 -0
- package/src/react/model-context/stub-tool.ts +14 -0
- package/src/react/model-context/toolbox.test.ts +182 -0
- package/src/react/model-context/toolbox.ts +189 -21
- package/src/react/model-context/useAssistantTool.ts +28 -8
- package/src/react/model-context/useAssistantToolUI.ts +13 -4
- package/src/react/model-context/useAuiToolOverrides.ts +38 -0
- package/src/react/primitives/part/PartMessages.tsx +13 -11
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +43 -0
- package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +9 -0
- package/src/runtime/api/thread-list-item-runtime.ts +15 -0
- package/src/runtime/interfaces/thread-list-runtime-core.ts +4 -0
- package/src/runtimes/external-store/external-store-adapter.ts +7 -0
- package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +13 -0
- package/src/runtimes/remote-thread-list/adapter/in-memory.ts +4 -0
- package/src/runtimes/remote-thread-list/types.ts +4 -0
- package/src/store/clients/model-context-client.test.ts +87 -2
- package/src/store/runtime-clients/thread-list-item-runtime-client.ts +1 -0
- package/src/store/scopes/thread-list-item.ts +1 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-custom-metadata.test.ts +69 -1
- package/src/tests/thread-list-runtime-getLoadThreadsPromise.test.ts +1 -0
|
@@ -14,6 +14,7 @@ import { InMemoryThreadListAdapter } from "../../../runtimes/remote-thread-list/
|
|
|
14
14
|
import { useAssistantCloudThreadHistoryAdapter } from "./AssistantCloudThreadHistoryAdapter";
|
|
15
15
|
import { RuntimeAdapterProvider } from "../RuntimeAdapterProvider";
|
|
16
16
|
import { CloudFileAttachmentAdapter } from "./CloudFileAttachmentAdapter";
|
|
17
|
+
import { isRecord } from "../../../utils/json/is-json";
|
|
17
18
|
|
|
18
19
|
type ThreadData = {
|
|
19
20
|
externalId: string | undefined;
|
|
@@ -26,6 +27,9 @@ type CloudThreadListAdapterOptions = {
|
|
|
26
27
|
delete?: ((threadId: string) => Promise<void>) | undefined;
|
|
27
28
|
};
|
|
28
29
|
|
|
30
|
+
const toCustom = (value: unknown): Record<string, unknown> | undefined =>
|
|
31
|
+
isRecord(value) ? value : undefined;
|
|
32
|
+
|
|
29
33
|
const baseUrl =
|
|
30
34
|
typeof process !== "undefined" &&
|
|
31
35
|
process?.env?.NEXT_PUBLIC_ASSISTANT_BASE_URL;
|
|
@@ -91,6 +95,7 @@ export const useCloudThreadListAdapter = (
|
|
|
91
95
|
remoteId: t.id,
|
|
92
96
|
title: t.title,
|
|
93
97
|
externalId: t.external_id ?? undefined,
|
|
98
|
+
custom: toCustom(t.metadata),
|
|
94
99
|
})),
|
|
95
100
|
};
|
|
96
101
|
},
|
|
@@ -110,6 +115,9 @@ export const useCloudThreadListAdapter = (
|
|
|
110
115
|
rename: async (threadId, newTitle) => {
|
|
111
116
|
return cloud.threads.update(threadId, { title: newTitle });
|
|
112
117
|
},
|
|
118
|
+
updateCustom: async (threadId, custom) => {
|
|
119
|
+
return cloud.threads.update(threadId, { metadata: custom ?? null });
|
|
120
|
+
},
|
|
113
121
|
archive: async (threadId) => {
|
|
114
122
|
return cloud.threads.update(threadId, { is_archived: true });
|
|
115
123
|
},
|
|
@@ -146,6 +154,7 @@ export const useCloudThreadListAdapter = (
|
|
|
146
154
|
remoteId: thread.id,
|
|
147
155
|
title: thread.title,
|
|
148
156
|
externalId: thread.external_id ?? undefined,
|
|
157
|
+
custom: toCustom(thread.metadata),
|
|
149
158
|
};
|
|
150
159
|
},
|
|
151
160
|
|
|
@@ -38,6 +38,7 @@ export type ThreadListItemRuntime = {
|
|
|
38
38
|
|
|
39
39
|
switchTo(options?: { unarchive?: boolean }): Promise<void>;
|
|
40
40
|
rename(newTitle: string): Promise<void>;
|
|
41
|
+
updateCustom(custom: Record<string, unknown> | undefined): Promise<void>;
|
|
41
42
|
archive(): Promise<void>;
|
|
42
43
|
unarchive(): Promise<void>;
|
|
43
44
|
delete(): Promise<void>;
|
|
@@ -74,6 +75,7 @@ export class ThreadListItemRuntimeImpl implements ThreadListItemRuntime {
|
|
|
74
75
|
protected __internal_bindMethods() {
|
|
75
76
|
this.switchTo = this.switchTo.bind(this);
|
|
76
77
|
this.rename = this.rename.bind(this);
|
|
78
|
+
this.updateCustom = this.updateCustom.bind(this);
|
|
77
79
|
this.archive = this.archive.bind(this);
|
|
78
80
|
this.unarchive = this.unarchive.bind(this);
|
|
79
81
|
this.delete = this.delete.bind(this);
|
|
@@ -100,6 +102,19 @@ export class ThreadListItemRuntimeImpl implements ThreadListItemRuntime {
|
|
|
100
102
|
return this._threadListBinding.rename(state.id, newTitle);
|
|
101
103
|
}
|
|
102
104
|
|
|
105
|
+
public updateCustom(
|
|
106
|
+
custom: Record<string, unknown> | undefined,
|
|
107
|
+
): Promise<void> {
|
|
108
|
+
const state = this._core.getState();
|
|
109
|
+
if (!this._threadListBinding.updateCustom) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
"Thread list runtime does not support updating custom metadata",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return this._threadListBinding.updateCustom(state.id, custom);
|
|
116
|
+
}
|
|
117
|
+
|
|
103
118
|
public archive(): Promise<void> {
|
|
104
119
|
const state = this._core.getState();
|
|
105
120
|
|
|
@@ -44,6 +44,10 @@ export type ThreadListRuntimeCore = {
|
|
|
44
44
|
|
|
45
45
|
detach(threadId: string): Promise<void>;
|
|
46
46
|
rename(threadId: string, newTitle: string): Promise<void>;
|
|
47
|
+
updateCustom?(
|
|
48
|
+
threadId: string,
|
|
49
|
+
custom: Record<string, unknown> | undefined,
|
|
50
|
+
): Promise<void>;
|
|
47
51
|
archive(threadId: string): Promise<void>;
|
|
48
52
|
unarchive(threadId: string): Promise<void>;
|
|
49
53
|
delete(threadId: string): Promise<void>;
|
|
@@ -24,6 +24,7 @@ export type ExternalStoreThreadData<TState extends "regular" | "archived"> = {
|
|
|
24
24
|
remoteId?: string | undefined;
|
|
25
25
|
externalId?: string | undefined;
|
|
26
26
|
title?: string | undefined;
|
|
27
|
+
custom?: Record<string, unknown> | undefined;
|
|
27
28
|
};
|
|
28
29
|
|
|
29
30
|
export type ExternalStoreThreadListAdapter = {
|
|
@@ -46,6 +47,12 @@ export type ExternalStoreThreadListAdapter = {
|
|
|
46
47
|
threadId: string,
|
|
47
48
|
newTitle: string,
|
|
48
49
|
) => (Promise<void> | void) | undefined;
|
|
50
|
+
onUpdateCustom?:
|
|
51
|
+
| ((
|
|
52
|
+
threadId: string,
|
|
53
|
+
custom: Record<string, unknown> | undefined,
|
|
54
|
+
) => Promise<void> | void)
|
|
55
|
+
| undefined;
|
|
49
56
|
onArchive?: ((threadId: string) => Promise<void> | void) | undefined;
|
|
50
57
|
onUnarchive?: ((threadId: string) => Promise<void> | void) | undefined;
|
|
51
58
|
onDelete?: ((threadId: string) => Promise<void> | void) | undefined;
|
|
@@ -197,6 +197,19 @@ export class ExternalStoreThreadListRuntimeCore implements ThreadListRuntimeCore
|
|
|
197
197
|
await onRename(threadId, newTitle);
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
public async updateCustom(
|
|
201
|
+
threadId: string,
|
|
202
|
+
custom: Record<string, unknown> | undefined,
|
|
203
|
+
): Promise<void> {
|
|
204
|
+
const onUpdateCustom = this.adapter.onUpdateCustom;
|
|
205
|
+
if (!onUpdateCustom)
|
|
206
|
+
throw new Error(
|
|
207
|
+
"External store adapter does not support updating custom metadata",
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
await onUpdateCustom(threadId, custom);
|
|
211
|
+
}
|
|
212
|
+
|
|
200
213
|
public async detach(): Promise<void> {
|
|
201
214
|
// no-op
|
|
202
215
|
}
|
|
@@ -29,6 +29,10 @@ export type RemoteThreadListAdapter = {
|
|
|
29
29
|
list(params?: RemoteThreadListPageOptions): Promise<RemoteThreadListResponse>;
|
|
30
30
|
|
|
31
31
|
rename(remoteId: string, newTitle: string): Promise<void>;
|
|
32
|
+
updateCustom?(
|
|
33
|
+
remoteId: string,
|
|
34
|
+
custom: Record<string, unknown> | undefined,
|
|
35
|
+
): Promise<void>;
|
|
32
36
|
archive(remoteId: string): Promise<void>;
|
|
33
37
|
unarchive(remoteId: string): Promise<void>;
|
|
34
38
|
delete(remoteId: string): Promise<void>;
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
ModelContext as ModelContextValue,
|
|
7
7
|
ModelContextProvider,
|
|
8
8
|
} from "../../model-context/types";
|
|
9
|
+
import { mergeModelContexts } from "../../model-context/types";
|
|
9
10
|
|
|
10
11
|
const tick = () => new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
11
12
|
|
|
@@ -13,7 +14,7 @@ const provider = (ctx: ModelContextValue): ModelContextProvider => ({
|
|
|
13
14
|
getModelContext: () => ctx,
|
|
14
15
|
});
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
+
const toolFixture = (): Tool<any, any> =>
|
|
17
18
|
({ description: "", parameters: {} as any }) as unknown as Tool<any, any>;
|
|
18
19
|
|
|
19
20
|
const render = () => {
|
|
@@ -51,7 +52,9 @@ describe("ModelContext", () => {
|
|
|
51
52
|
try {
|
|
52
53
|
sub
|
|
53
54
|
.getValue()
|
|
54
|
-
.register(
|
|
55
|
+
.register(
|
|
56
|
+
provider({ tools: { foo: toolFixture(), bar: toolFixture() } }),
|
|
57
|
+
);
|
|
55
58
|
await tick();
|
|
56
59
|
|
|
57
60
|
expect(sub.getValue().getState().toolNames).toEqual(["bar", "foo"]);
|
|
@@ -106,3 +109,85 @@ describe("ModelContext", () => {
|
|
|
106
109
|
}
|
|
107
110
|
});
|
|
108
111
|
});
|
|
112
|
+
|
|
113
|
+
describe("mergeModelContexts", () => {
|
|
114
|
+
it("merges a higher-priority tool override into an existing tool", () => {
|
|
115
|
+
const execute = async () => ({ ok: true });
|
|
116
|
+
const merged = mergeModelContexts(
|
|
117
|
+
new Set([
|
|
118
|
+
provider({
|
|
119
|
+
priority: 0,
|
|
120
|
+
tools: {
|
|
121
|
+
add_task: {
|
|
122
|
+
type: "frontend",
|
|
123
|
+
description: "Add a task",
|
|
124
|
+
parameters: { type: "object", properties: {} } as any,
|
|
125
|
+
renderText: { running: "Adding task" },
|
|
126
|
+
} as Tool<any, any>,
|
|
127
|
+
},
|
|
128
|
+
}),
|
|
129
|
+
provider({
|
|
130
|
+
priority: 1000,
|
|
131
|
+
tools: {
|
|
132
|
+
add_task: {
|
|
133
|
+
execute,
|
|
134
|
+
} as unknown as Tool<any, any>,
|
|
135
|
+
},
|
|
136
|
+
}),
|
|
137
|
+
]),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
expect(merged.tools?.add_task).toMatchObject({
|
|
141
|
+
type: "frontend",
|
|
142
|
+
description: "Add a task",
|
|
143
|
+
});
|
|
144
|
+
expect(merged.tools?.add_task?.execute).toBe(execute);
|
|
145
|
+
expect(merged.tools?.add_task?.parameters).toEqual({
|
|
146
|
+
type: "object",
|
|
147
|
+
properties: {},
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("still rejects duplicate tools at the same priority", () => {
|
|
152
|
+
expect(() =>
|
|
153
|
+
mergeModelContexts(
|
|
154
|
+
new Set([
|
|
155
|
+
provider({ tools: { duplicate: toolFixture() } }),
|
|
156
|
+
provider({ tools: { duplicate: toolFixture() } }),
|
|
157
|
+
]),
|
|
158
|
+
),
|
|
159
|
+
).toThrow(/already exists/);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("preserves the highest priority when a lower-priority provider reuses the same tool object", () => {
|
|
163
|
+
const shared = {
|
|
164
|
+
...toolFixture(),
|
|
165
|
+
description: "high priority",
|
|
166
|
+
} as Tool<any, any>;
|
|
167
|
+
const execute = async () => ({ ok: true });
|
|
168
|
+
const merged = mergeModelContexts(
|
|
169
|
+
new Set([
|
|
170
|
+
provider({
|
|
171
|
+
priority: 1000,
|
|
172
|
+
tools: { shared },
|
|
173
|
+
}),
|
|
174
|
+
provider({
|
|
175
|
+
priority: 0,
|
|
176
|
+
tools: { shared },
|
|
177
|
+
}),
|
|
178
|
+
provider({
|
|
179
|
+
priority: 500,
|
|
180
|
+
tools: {
|
|
181
|
+
shared: {
|
|
182
|
+
description: "medium priority",
|
|
183
|
+
execute,
|
|
184
|
+
} as unknown as Tool<any, any>,
|
|
185
|
+
},
|
|
186
|
+
}),
|
|
187
|
+
]),
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
expect(merged.tools?.shared?.description).toBe("high priority");
|
|
191
|
+
expect(merged.tools?.shared?.execute).toBe(execute);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -14,6 +14,7 @@ export type ThreadListItemMethods = {
|
|
|
14
14
|
getState(): ThreadListItemState;
|
|
15
15
|
switchTo(options?: { unarchive?: boolean }): void;
|
|
16
16
|
rename(newTitle: string): void;
|
|
17
|
+
updateCustom(custom: Record<string, unknown> | undefined): void;
|
|
17
18
|
archive(): void;
|
|
18
19
|
unarchive(): void;
|
|
19
20
|
delete(): void;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
createCore,
|
|
4
|
+
deferred,
|
|
5
|
+
makeAdapter,
|
|
6
|
+
} from "./remote-thread-list-test-helpers";
|
|
3
7
|
|
|
4
8
|
describe("RemoteThreadListThreadListRuntimeCore custom metadata", () => {
|
|
5
9
|
it("preserves custom field from list() through to threadItems", async () => {
|
|
@@ -120,4 +124,68 @@ describe("RemoteThreadListThreadListRuntimeCore custom metadata", () => {
|
|
|
120
124
|
workspaceId: "ws-1",
|
|
121
125
|
});
|
|
122
126
|
});
|
|
127
|
+
|
|
128
|
+
it("updates custom through the adapter with optimistic state", async () => {
|
|
129
|
+
const updateDeferred = deferred<void>();
|
|
130
|
+
const adapter = makeAdapter({
|
|
131
|
+
list: vi.fn(async () => ({
|
|
132
|
+
threads: [
|
|
133
|
+
{
|
|
134
|
+
status: "regular" as const,
|
|
135
|
+
remoteId: "thread-6",
|
|
136
|
+
externalId: "ext-6",
|
|
137
|
+
title: "Test",
|
|
138
|
+
custom: { tag: "old" },
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
})),
|
|
142
|
+
updateCustom: vi.fn(() => updateDeferred.promise),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const core = createCore(adapter);
|
|
146
|
+
await core.getLoadThreadsPromise();
|
|
147
|
+
|
|
148
|
+
const updateTask = core.updateCustom("thread-6", { tag: "new" });
|
|
149
|
+
|
|
150
|
+
await Promise.resolve();
|
|
151
|
+
|
|
152
|
+
expect(adapter.updateCustom).toHaveBeenCalledWith("thread-6", {
|
|
153
|
+
tag: "new",
|
|
154
|
+
});
|
|
155
|
+
expect(core.getItemById("thread-6")?.custom).toEqual({ tag: "new" });
|
|
156
|
+
|
|
157
|
+
updateDeferred.resolve();
|
|
158
|
+
await updateTask;
|
|
159
|
+
|
|
160
|
+
expect(core.getItemById("thread-6")?.custom).toEqual({ tag: "new" });
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("rolls back custom when adapter update fails", async () => {
|
|
164
|
+
const updateDeferred = deferred<void>();
|
|
165
|
+
const adapter = makeAdapter({
|
|
166
|
+
list: vi.fn(async () => ({
|
|
167
|
+
threads: [
|
|
168
|
+
{
|
|
169
|
+
status: "regular" as const,
|
|
170
|
+
remoteId: "thread-7",
|
|
171
|
+
externalId: "ext-7",
|
|
172
|
+
title: "Test",
|
|
173
|
+
custom: { tag: "old" },
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
})),
|
|
177
|
+
updateCustom: vi.fn(() => updateDeferred.promise),
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const core = createCore(adapter);
|
|
181
|
+
await core.getLoadThreadsPromise();
|
|
182
|
+
|
|
183
|
+
const updateTask = core.updateCustom("thread-7", { tag: "new" });
|
|
184
|
+
expect(core.getItemById("thread-7")?.custom).toEqual({ tag: "new" });
|
|
185
|
+
|
|
186
|
+
updateDeferred.reject(new Error("update failed"));
|
|
187
|
+
await expect(updateTask).rejects.toThrow("update failed");
|
|
188
|
+
|
|
189
|
+
expect(core.getItemById("thread-7")?.custom).toEqual({ tag: "old" });
|
|
190
|
+
});
|
|
123
191
|
});
|
|
@@ -41,6 +41,7 @@ const createMockCore = (
|
|
|
41
41
|
getLoadThreadsPromise: () => loadPromise,
|
|
42
42
|
detach: () => Promise.resolve(),
|
|
43
43
|
rename: () => Promise.resolve(),
|
|
44
|
+
updateCustom: () => Promise.resolve(),
|
|
44
45
|
archive: () => Promise.resolve(),
|
|
45
46
|
unarchive: () => Promise.resolve(),
|
|
46
47
|
delete: () => Promise.resolve(),
|