feat(workspaces): scaffold feature
Scaffold the workspaces feature package via pnpm turbo gen feature (single Workspace entity, getWorkspace use case) and hand-wire the aggregators: bindAll dispatcher (web-next), core-api app router, and workspace deps. Release-please registration reverted per the root-only versioning policy (AGENTS.md, ADR-027 retrofit). Pinned @trpc/server to the repo-wide 11.16.0 resolution so instanceof TRPCError checks share one module instance. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
import type { Workspace } from "../../entities/models/workspace";
|
||||
|
||||
export interface IWorkspaceRepository {
|
||||
getWorkspace(id: string): Promise<Workspace | null>;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { ZodError } from "zod";
|
||||
import { getWorkspaceUseCase } from "@/application/use-cases/get-workspace.use-case";
|
||||
import { MockWorkspaceRepository } from "@/infrastructure/repositories/workspace.repository.mock";
|
||||
import { WorkspaceNotFoundError } from "@/entities/errors/workspace";
|
||||
|
||||
describe("getWorkspaceUseCase", () => {
|
||||
it("returns the seeded workspace by id", async () => {
|
||||
const repo = new MockWorkspaceRepository();
|
||||
const useCase = getWorkspaceUseCase(repo);
|
||||
const result = await useCase({ id: "seed-1" });
|
||||
expect(result.id).toBe("seed-1");
|
||||
expect(result.name).toBeTypeOf("string");
|
||||
});
|
||||
|
||||
it("throws WorkspaceNotFoundError when repository returns null", async () => {
|
||||
const repo = new MockWorkspaceRepository(new Map());
|
||||
const useCase = getWorkspaceUseCase(repo);
|
||||
await expect(useCase({ id: "missing" })).rejects.toBeInstanceOf(
|
||||
WorkspaceNotFoundError,
|
||||
);
|
||||
});
|
||||
|
||||
it("throws ZodError when repository returns malformed data", async () => {
|
||||
const malformedRepo = {
|
||||
getWorkspace: async () => ({ id: "", name: "x" }) as never,
|
||||
};
|
||||
const useCase = getWorkspaceUseCase(malformedRepo);
|
||||
await expect(useCase({ id: "anything" })).rejects.toBeInstanceOf(ZodError);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { WorkspaceNotFoundError } from "../../entities/errors/workspace";
|
||||
import { workspaceSchema } from "../../entities/models/workspace";
|
||||
import type { IWorkspaceRepository } from "../repositories/workspace.repository.interface";
|
||||
|
||||
// ── Input ────────────────────────────────────────────────────────────────
|
||||
export const getWorkspaceInputSchema = z
|
||||
.object({
|
||||
id: z.string().min(1),
|
||||
})
|
||||
.strict();
|
||||
export type GetWorkspaceInput = z.infer<typeof getWorkspaceInputSchema>;
|
||||
|
||||
// ── Output ───────────────────────────────────────────────────────────────
|
||||
export const getWorkspaceOutputSchema = workspaceSchema;
|
||||
export type GetWorkspaceOutput = z.infer<typeof getWorkspaceOutputSchema>;
|
||||
|
||||
// ── Use case ─────────────────────────────────────────────────────────────
|
||||
export type IGetWorkspaceUseCase = ReturnType<typeof getWorkspaceUseCase>;
|
||||
|
||||
export const getWorkspaceUseCase =
|
||||
(workspaceRepository: IWorkspaceRepository) =>
|
||||
async (input: GetWorkspaceInput): Promise<GetWorkspaceOutput> => {
|
||||
const result = await workspaceRepository.getWorkspace(input.id);
|
||||
if (!result) {
|
||||
throw new WorkspaceNotFoundError(`Workspace not found: ${input.id}`);
|
||||
}
|
||||
return getWorkspaceOutputSchema.parse(result);
|
||||
};
|
||||
Reference in New Issue
Block a user