summaryrefslogtreecommitdiffhomepage
path: root/src/features/workspaces/adapter
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-26 22:21:55 +0900
committerAdam Malczewski <[email protected]>2026-06-26 22:23:39 +0900
commitc333fcec32b1f90bf0da6bb14d2609c20e38a74f (patch)
tree0db3ec77a6838c4f800c362df0de3c6cd8544431 /src/features/workspaces/adapter
parent1285564f12238b22f6b39b9f3fbcecaca8456911 (diff)
downloaddispatch-web-c333fcec32b1f90bf0da6bb14d2609c20e38a74f.tar.gz
dispatch-web-c333fcec32b1f90bf0da6bb14d2609c20e38a74f.zip
style: switch from tabs to 2-space indentation (incl. svelte)
Diffstat (limited to 'src/features/workspaces/adapter')
-rw-r--r--src/features/workspaces/adapter/http.test.ts224
-rw-r--r--src/features/workspaces/adapter/http.ts242
2 files changed, 233 insertions, 233 deletions
diff --git a/src/features/workspaces/adapter/http.test.ts b/src/features/workspaces/adapter/http.test.ts
index adaff00..19e53f8 100644
--- a/src/features/workspaces/adapter/http.test.ts
+++ b/src/features/workspaces/adapter/http.test.ts
@@ -3,131 +3,131 @@ import { createWorkspaceHttp } from "./http";
/** Build a fake `fetch` returning a canned Response. */
function fakeFetch(responses: Array<{ status?: number; body?: unknown } | Error>): typeof fetch {
- let i = 0;
- return vi.fn(async () => {
- const next = responses[i++];
- if (next === undefined) throw new Error("fakeFetch: no more canned responses");
- if (next instanceof Error) throw next;
- const status = next.status ?? 200;
- const body = next.body;
- return {
- ok: status >= 200 && status < 300,
- status,
- async json() {
- return body;
- },
- } as Response;
- }) as unknown as typeof fetch;
+ let i = 0;
+ return vi.fn(async () => {
+ const next = responses[i++];
+ if (next === undefined) throw new Error("fakeFetch: no more canned responses");
+ if (next instanceof Error) throw next;
+ const status = next.status ?? 200;
+ const body = next.body;
+ return {
+ ok: status >= 200 && status < 300,
+ status,
+ async json() {
+ return body;
+ },
+ } as Response;
+ }) as unknown as typeof fetch;
}
const BASE = "http://x";
describe("createWorkspaceHttp", () => {
- it("list returns the workspaces", async () => {
- const fetchImpl = fakeFetch([
- {
- body: {
- workspaces: [
- {
- id: "a",
- title: "A",
- defaultCwd: null,
- createdAt: 1,
- lastActivityAt: 2,
- conversationCount: 3,
- },
- ],
- },
- },
- ]);
- const http = createWorkspaceHttp(BASE, fetchImpl);
- const list = await http.list();
- expect(list).toHaveLength(1);
- expect(list[0]?.id).toBe("a");
- expect(list[0]?.conversationCount).toBe(3);
- expect(fetchImpl).toHaveBeenCalledWith(`${BASE}/workspaces`);
- });
+ it("list returns the workspaces", async () => {
+ const fetchImpl = fakeFetch([
+ {
+ body: {
+ workspaces: [
+ {
+ id: "a",
+ title: "A",
+ defaultCwd: null,
+ createdAt: 1,
+ lastActivityAt: 2,
+ conversationCount: 3,
+ },
+ ],
+ },
+ },
+ ]);
+ const http = createWorkspaceHttp(BASE, fetchImpl);
+ const list = await http.list();
+ expect(list).toHaveLength(1);
+ expect(list[0]?.id).toBe("a");
+ expect(list[0]?.conversationCount).toBe(3);
+ expect(fetchImpl).toHaveBeenCalledWith(`${BASE}/workspaces`);
+ });
- it("list returns [] on a failed response (non-fatal)", async () => {
- const http = createWorkspaceHttp(BASE, fakeFetch([{ status: 500 }]));
- expect(await http.list()).toEqual([]);
- });
+ it("list returns [] on a failed response (non-fatal)", async () => {
+ const http = createWorkspaceHttp(BASE, fakeFetch([{ status: 500 }]));
+ expect(await http.list()).toEqual([]);
+ });
- it("list returns [] on a network error", async () => {
- const http = createWorkspaceHttp(BASE, fakeFetch([new Error("network")]));
- expect(await http.list()).toEqual([]);
- });
+ it("list returns [] on a network error", async () => {
+ const http = createWorkspaceHttp(BASE, fakeFetch([new Error("network")]));
+ expect(await http.list()).toEqual([]);
+ });
- it("ensure PUTs the id + returns the workspace", async () => {
- const ws = { id: "my-ws", title: "my-ws", defaultCwd: null, createdAt: 10, lastActivityAt: 10 };
- const fetchImpl = fakeFetch([{ body: ws }]);
- const http = createWorkspaceHttp(BASE, fetchImpl);
- const result = await http.ensure("my-ws");
- expect(result).toEqual({ ok: true, value: ws });
- expect(fetchImpl).toHaveBeenCalledWith(
- `${BASE}/workspaces/my-ws`,
- expect.objectContaining({ method: "PUT" }),
- );
- });
+ it("ensure PUTs the id + returns the workspace", async () => {
+ const ws = { id: "my-ws", title: "my-ws", defaultCwd: null, createdAt: 10, lastActivityAt: 10 };
+ const fetchImpl = fakeFetch([{ body: ws }]);
+ const http = createWorkspaceHttp(BASE, fetchImpl);
+ const result = await http.ensure("my-ws");
+ expect(result).toEqual({ ok: true, value: ws });
+ expect(fetchImpl).toHaveBeenCalledWith(
+ `${BASE}/workspaces/my-ws`,
+ expect.objectContaining({ method: "PUT" }),
+ );
+ });
- it("ensure surfaces the backend error on a 400 (invalid slug)", async () => {
- const http = createWorkspaceHttp(
- BASE,
- fakeFetch([{ status: 400, body: { error: "invalid slug" } }]),
- );
- const result = await http.ensure("UPPER");
- expect(result).toEqual({ ok: false, error: "invalid slug" });
- });
+ it("ensure surfaces the backend error on a 400 (invalid slug)", async () => {
+ const http = createWorkspaceHttp(
+ BASE,
+ fakeFetch([{ status: 400, body: { error: "invalid slug" } }]),
+ );
+ const result = await http.ensure("UPPER");
+ expect(result).toEqual({ ok: false, error: "invalid slug" });
+ });
- it("get returns null on 404", async () => {
- const http = createWorkspaceHttp(BASE, fakeFetch([{ status: 404 }]));
- expect(await http.get("nope")).toBeNull();
- });
+ it("get returns null on 404", async () => {
+ const http = createWorkspaceHttp(BASE, fakeFetch([{ status: 404 }]));
+ expect(await http.get("nope")).toBeNull();
+ });
- it("get returns the workspace on 200", async () => {
- const ws = { id: "x", title: "X", defaultCwd: "/home", createdAt: 1, lastActivityAt: 2 };
- const http = createWorkspaceHttp(BASE, fakeFetch([{ body: ws }]));
- expect(await http.get("x")).toEqual(ws);
- });
+ it("get returns the workspace on 200", async () => {
+ const ws = { id: "x", title: "X", defaultCwd: "/home", createdAt: 1, lastActivityAt: 2 };
+ const http = createWorkspaceHttp(BASE, fakeFetch([{ body: ws }]));
+ expect(await http.get("x")).toEqual(ws);
+ });
- it("setTitle PUTs the title", async () => {
- const ws = { id: "a", title: "Renamed", defaultCwd: null, createdAt: 1, lastActivityAt: 2 };
- const fetchImpl = fakeFetch([{ body: ws }]);
- const http = createWorkspaceHttp(BASE, fetchImpl);
- const result = await http.setTitle("a", "Renamed");
- expect(result).toEqual({ ok: true, value: ws });
- const call = (fetchImpl as unknown as ReturnType<typeof vi.fn>).mock.calls[0];
- expect(call?.[0]).toBe(`${BASE}/workspaces/a/title`);
- expect(JSON.parse(call?.[1]?.body)).toEqual({ title: "Renamed" });
- });
+ it("setTitle PUTs the title", async () => {
+ const ws = { id: "a", title: "Renamed", defaultCwd: null, createdAt: 1, lastActivityAt: 2 };
+ const fetchImpl = fakeFetch([{ body: ws }]);
+ const http = createWorkspaceHttp(BASE, fetchImpl);
+ const result = await http.setTitle("a", "Renamed");
+ expect(result).toEqual({ ok: true, value: ws });
+ const call = (fetchImpl as unknown as ReturnType<typeof vi.fn>).mock.calls[0];
+ expect(call?.[0]).toBe(`${BASE}/workspaces/a/title`);
+ expect(JSON.parse(call?.[1]?.body)).toEqual({ title: "Renamed" });
+ });
- it("setDefaultCwd PUTs null to clear", async () => {
- const ws = { id: "a", title: "A", defaultCwd: null, createdAt: 1, lastActivityAt: 2 };
- const fetchImpl = fakeFetch([{ body: ws }]);
- const http = createWorkspaceHttp(BASE, fetchImpl);
- await http.setDefaultCwd("a", null);
- const call = (fetchImpl as unknown as ReturnType<typeof vi.fn>).mock.calls[0];
- expect(call?.[0]).toBe(`${BASE}/workspaces/a/default-cwd`);
- expect(JSON.parse(call?.[1]?.body)).toEqual({ defaultCwd: null });
- });
+ it("setDefaultCwd PUTs null to clear", async () => {
+ const ws = { id: "a", title: "A", defaultCwd: null, createdAt: 1, lastActivityAt: 2 };
+ const fetchImpl = fakeFetch([{ body: ws }]);
+ const http = createWorkspaceHttp(BASE, fetchImpl);
+ await http.setDefaultCwd("a", null);
+ const call = (fetchImpl as unknown as ReturnType<typeof vi.fn>).mock.calls[0];
+ expect(call?.[0]).toBe(`${BASE}/workspaces/a/default-cwd`);
+ expect(JSON.parse(call?.[1]?.body)).toEqual({ defaultCwd: null });
+ });
- it("delete returns closedCount", async () => {
- const fetchImpl = fakeFetch([{ body: { workspaceId: "a", closedCount: 4 } }]);
- const http = createWorkspaceHttp(BASE, fetchImpl);
- const result = await http.delete("a");
- expect(result).toEqual({ ok: true, value: { closedCount: 4 } });
- expect(fetchImpl).toHaveBeenCalledWith(
- `${BASE}/workspaces/a`,
- expect.objectContaining({ method: "DELETE" }),
- );
- });
+ it("delete returns closedCount", async () => {
+ const fetchImpl = fakeFetch([{ body: { workspaceId: "a", closedCount: 4 } }]);
+ const http = createWorkspaceHttp(BASE, fetchImpl);
+ const result = await http.delete("a");
+ expect(result).toEqual({ ok: true, value: { closedCount: 4 } });
+ expect(fetchImpl).toHaveBeenCalledWith(
+ `${BASE}/workspaces/a`,
+ expect.objectContaining({ method: "DELETE" }),
+ );
+ });
- it("delete surfaces 409 for 'default'", async () => {
- const http = createWorkspaceHttp(
- BASE,
- fakeFetch([{ status: 409, body: { error: "cannot delete default" } }]),
- );
- const result = await http.delete("default");
- expect(result).toEqual({ ok: false, error: "cannot delete default" });
- });
+ it("delete surfaces 409 for 'default'", async () => {
+ const http = createWorkspaceHttp(
+ BASE,
+ fakeFetch([{ status: 409, body: { error: "cannot delete default" } }]),
+ );
+ const result = await http.delete("default");
+ expect(result).toEqual({ ok: false, error: "cannot delete default" });
+ });
});
diff --git a/src/features/workspaces/adapter/http.ts b/src/features/workspaces/adapter/http.ts
index 6ebfee1..01fe677 100644
--- a/src/features/workspaces/adapter/http.ts
+++ b/src/features/workspaces/adapter/http.ts
@@ -1,13 +1,13 @@
import type {
- DeleteWorkspaceResponse,
- EnsureWorkspaceRequest,
- SetWorkspaceDefaultComputerRequest,
- SetWorkspaceDefaultCwdRequest,
- SetWorkspaceTitleRequest,
- Workspace,
- WorkspaceEntry,
- WorkspaceListResponse,
- WorkspaceResponse,
+ DeleteWorkspaceResponse,
+ EnsureWorkspaceRequest,
+ SetWorkspaceDefaultComputerRequest,
+ SetWorkspaceDefaultCwdRequest,
+ SetWorkspaceTitleRequest,
+ Workspace,
+ WorkspaceEntry,
+ WorkspaceListResponse,
+ WorkspaceResponse,
} from "@dispatch/transport-contract";
/**
@@ -28,130 +28,130 @@ import type {
* - `DELETE /workspaces/:id` (409 for "default") → delete
*/
export type WorkspaceResult<T> =
- | { readonly ok: true; readonly value: T }
- | { readonly ok: false; readonly error: string };
+ | { readonly ok: true; readonly value: T }
+ | { readonly ok: false; readonly error: string };
export interface WorkspaceHttp {
- list(): Promise<readonly WorkspaceEntry[]>;
- ensure(id: string, body?: EnsureWorkspaceRequest): Promise<WorkspaceResult<Workspace>>;
- get(id: string): Promise<Workspace | null>;
- setTitle(id: string, title: string): Promise<WorkspaceResult<Workspace>>;
- setDefaultCwd(id: string, defaultCwd: string | null): Promise<WorkspaceResult<Workspace>>;
- setDefaultComputer(id: string, computerId: string | null): Promise<WorkspaceResult<Workspace>>;
- delete(id: string): Promise<WorkspaceResult<{ closedCount: number }>>;
+ list(): Promise<readonly WorkspaceEntry[]>;
+ ensure(id: string, body?: EnsureWorkspaceRequest): Promise<WorkspaceResult<Workspace>>;
+ get(id: string): Promise<Workspace | null>;
+ setTitle(id: string, title: string): Promise<WorkspaceResult<Workspace>>;
+ setDefaultCwd(id: string, defaultCwd: string | null): Promise<WorkspaceResult<Workspace>>;
+ setDefaultComputer(id: string, computerId: string | null): Promise<WorkspaceResult<Workspace>>;
+ delete(id: string): Promise<WorkspaceResult<{ closedCount: number }>>;
}
async function errText(res: Response): Promise<string> {
- try {
- const body = (await res.json()) as { error?: string };
- return body.error ?? `HTTP ${res.status}`;
- } catch {
- return `HTTP ${res.status}`;
- }
+ try {
+ const body = (await res.json()) as { error?: string };
+ return body.error ?? `HTTP ${res.status}`;
+ } catch {
+ return `HTTP ${res.status}`;
+ }
}
export function createWorkspaceHttp(httpBase: string, fetchImpl: typeof fetch): WorkspaceHttp {
- return {
- async list(): Promise<readonly WorkspaceEntry[]> {
- try {
- const res = await fetchImpl(`${httpBase}/workspaces`);
- if (!res.ok) return [];
- const data = (await res.json()) as WorkspaceListResponse;
- return data.workspaces;
- } catch {
- return [];
- }
- },
+ return {
+ async list(): Promise<readonly WorkspaceEntry[]> {
+ try {
+ const res = await fetchImpl(`${httpBase}/workspaces`);
+ if (!res.ok) return [];
+ const data = (await res.json()) as WorkspaceListResponse;
+ return data.workspaces;
+ } catch {
+ return [];
+ }
+ },
- async ensure(id, body): Promise<WorkspaceResult<Workspace>> {
- try {
- const res = await fetchImpl(`${httpBase}/workspaces/${encodeURIComponent(id)}`, {
- method: "PUT",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(body ?? {}),
- });
- if (!res.ok) return { ok: false, error: await errText(res) };
- return { ok: true, value: (await res.json()) as WorkspaceResponse };
- } catch (err) {
- return {
- ok: false,
- error: err instanceof Error ? err.message : "Workspace request failed",
- };
- }
- },
+ async ensure(id, body): Promise<WorkspaceResult<Workspace>> {
+ try {
+ const res = await fetchImpl(`${httpBase}/workspaces/${encodeURIComponent(id)}`, {
+ method: "PUT",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(body ?? {}),
+ });
+ if (!res.ok) return { ok: false, error: await errText(res) };
+ return { ok: true, value: (await res.json()) as WorkspaceResponse };
+ } catch (err) {
+ return {
+ ok: false,
+ error: err instanceof Error ? err.message : "Workspace request failed",
+ };
+ }
+ },
- async get(id): Promise<Workspace | null> {
- try {
- const res = await fetchImpl(`${httpBase}/workspaces/${encodeURIComponent(id)}`);
- if (res.status === 404 || !res.ok) return null;
- return (await res.json()) as WorkspaceResponse;
- } catch {
- return null;
- }
- },
+ async get(id): Promise<Workspace | null> {
+ try {
+ const res = await fetchImpl(`${httpBase}/workspaces/${encodeURIComponent(id)}`);
+ if (res.status === 404 || !res.ok) return null;
+ return (await res.json()) as WorkspaceResponse;
+ } catch {
+ return null;
+ }
+ },
- async setTitle(id, title): Promise<WorkspaceResult<Workspace>> {
- try {
- const res = await fetchImpl(`${httpBase}/workspaces/${encodeURIComponent(id)}/title`, {
- method: "PUT",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ title } satisfies SetWorkspaceTitleRequest),
- });
- if (!res.ok) return { ok: false, error: await errText(res) };
- return { ok: true, value: (await res.json()) as WorkspaceResponse };
- } catch (err) {
- return { ok: false, error: err instanceof Error ? err.message : "Rename failed" };
- }
- },
+ async setTitle(id, title): Promise<WorkspaceResult<Workspace>> {
+ try {
+ const res = await fetchImpl(`${httpBase}/workspaces/${encodeURIComponent(id)}/title`, {
+ method: "PUT",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ title } satisfies SetWorkspaceTitleRequest),
+ });
+ if (!res.ok) return { ok: false, error: await errText(res) };
+ return { ok: true, value: (await res.json()) as WorkspaceResponse };
+ } catch (err) {
+ return { ok: false, error: err instanceof Error ? err.message : "Rename failed" };
+ }
+ },
- async setDefaultCwd(id, defaultCwd): Promise<WorkspaceResult<Workspace>> {
- try {
- const res = await fetchImpl(
- `${httpBase}/workspaces/${encodeURIComponent(id)}/default-cwd`,
- {
- method: "PUT",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ defaultCwd } satisfies SetWorkspaceDefaultCwdRequest),
- },
- );
- if (!res.ok) return { ok: false, error: await errText(res) };
- return { ok: true, value: (await res.json()) as WorkspaceResponse };
- } catch (err) {
- return { ok: false, error: err instanceof Error ? err.message : "Set default cwd failed" };
- }
- },
+ async setDefaultCwd(id, defaultCwd): Promise<WorkspaceResult<Workspace>> {
+ try {
+ const res = await fetchImpl(
+ `${httpBase}/workspaces/${encodeURIComponent(id)}/default-cwd`,
+ {
+ method: "PUT",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ defaultCwd } satisfies SetWorkspaceDefaultCwdRequest),
+ },
+ );
+ if (!res.ok) return { ok: false, error: await errText(res) };
+ return { ok: true, value: (await res.json()) as WorkspaceResponse };
+ } catch (err) {
+ return { ok: false, error: err instanceof Error ? err.message : "Set default cwd failed" };
+ }
+ },
- async setDefaultComputer(id, computerId): Promise<WorkspaceResult<Workspace>> {
- try {
- const res = await fetchImpl(
- `${httpBase}/workspaces/${encodeURIComponent(id)}/default-computer`,
- {
- method: "PUT",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ computerId } satisfies SetWorkspaceDefaultComputerRequest),
- },
- );
- if (!res.ok) return { ok: false, error: await errText(res) };
- return { ok: true, value: (await res.json()) as WorkspaceResponse };
- } catch (err) {
- return {
- ok: false,
- error: err instanceof Error ? err.message : "Set default computer failed",
- };
- }
- },
+ async setDefaultComputer(id, computerId): Promise<WorkspaceResult<Workspace>> {
+ try {
+ const res = await fetchImpl(
+ `${httpBase}/workspaces/${encodeURIComponent(id)}/default-computer`,
+ {
+ method: "PUT",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ computerId } satisfies SetWorkspaceDefaultComputerRequest),
+ },
+ );
+ if (!res.ok) return { ok: false, error: await errText(res) };
+ return { ok: true, value: (await res.json()) as WorkspaceResponse };
+ } catch (err) {
+ return {
+ ok: false,
+ error: err instanceof Error ? err.message : "Set default computer failed",
+ };
+ }
+ },
- async delete(id): Promise<WorkspaceResult<{ closedCount: number }>> {
- try {
- const res = await fetchImpl(`${httpBase}/workspaces/${encodeURIComponent(id)}`, {
- method: "DELETE",
- });
- if (!res.ok) return { ok: false, error: await errText(res) };
- const data = (await res.json()) as DeleteWorkspaceResponse;
- return { ok: true, value: { closedCount: data.closedCount } };
- } catch (err) {
- return { ok: false, error: err instanceof Error ? err.message : "Delete failed" };
- }
- },
- };
+ async delete(id): Promise<WorkspaceResult<{ closedCount: number }>> {
+ try {
+ const res = await fetchImpl(`${httpBase}/workspaces/${encodeURIComponent(id)}`, {
+ method: "DELETE",
+ });
+ if (!res.ok) return { ok: false, error: await errText(res) };
+ const data = (await res.json()) as DeleteWorkspaceResponse;
+ return { ok: true, value: { closedCount: data.closedCount } };
+ } catch (err) {
+ return { ok: false, error: err instanceof Error ? err.message : "Delete failed" };
+ }
+ },
+ };
}