refactor: 프론트엔드 리펙토링

This commit is contained in:
donghyeon-ka
2026-09-18 22:05:42 +09:00
parent 5cc41467ae
commit ec7f20e2ee
100 changed files with 6005 additions and 2867 deletions
@@ -3,57 +3,129 @@ import { describe, expect, it, vi } from "vitest";
import {
createFeatureHttpBinding,
defineFeatureHttpOperation,
type HttpExecutionOutcome,
type InstalledHttpOperationExecutor,
} from "../../../src/adapters/http/index.ts";
import {
mappingFailure,
mappingSuccess,
} from "../../../src/contracts/boundary-mapper.ts";
import type {
InstalledHttpContract,
RuntimeValidator,
} from "../../../src/contracts/external-contract-runtime.ts";
type ResourceInput = Readonly<{ resourceId: string }>;
type ResourceWire = Readonly<{ id: string; title: string }>;
type ResourceProblem = Readonly<{ code?: string }>;
type Resource = Readonly<{ id: string; title: string }>;
const OPERATIONS = Object.freeze({
LOAD_RESOURCE: defineFeatureHttpOperation<
Readonly<{ resourceId: string }>,
Resource
>({
function validator<T>(schemaId: string): RuntimeValidator<T> {
return Object.freeze({
schemaId,
safeParse(value: unknown) {
return Object.freeze({
success: true as const,
data: value as T,
});
},
});
}
const LOAD_RESOURCE_CONTRACT = Object.freeze({
contract: Object.freeze({
operationId: "LOAD_RESOURCE",
method: "GET" as const,
pathTemplate: "/resources/{resourceId}",
inputValidator: validator<ResourceInput>("ResourceInput"),
outputValidator: validator<ResourceWire>("ResourceWire"),
problemValidator: validator<ResourceProblem>("ResourceProblem"),
acceptedStatuses: Object.freeze([200]),
emptyBodyStatuses: Object.freeze([]),
retrySemantics: "SAFE" as const,
requestBody: "NONE" as const,
responseBody: "REQUIRED_JSON" as const,
commandRecovery: null,
commandEffect: null,
projectRequest(input: ResourceInput) {
return Object.freeze({
pathValues: Object.freeze({ resourceId: input.resourceId }),
queryEntries: Object.freeze([]),
body: null,
});
},
}),
frontend: Object.freeze({
policyId: "TEST_LOAD_RESOURCE",
requestByteLimit: 0,
responseByteLimit: 8_192,
totalDeadlineMs: 1_000,
retryBudget: 0 as const,
authProfileId: "TEST",
diagnosticsOperation: "test.load-resource",
}),
}) satisfies InstalledHttpContract<ResourceInput, ResourceWire, ResourceProblem>;
const OPERATIONS = Object.freeze({
LOAD_RESOURCE: defineFeatureHttpOperation({
contract: LOAD_RESOURCE_CONTRACT,
routeId: "RESOURCE_DETAIL",
mapSuccess(value) {
if (
!value ||
typeof value !== "object" ||
typeof (value as Record<string, unknown>).id !== "string" ||
typeof (value as Record<string, unknown>).title !== "string"
) {
if (value.title.length === 0) {
return mappingFailure("MAPPING_INVARIANT_REJECTED");
}
const candidate = value as Readonly<{ id: string; title: string }>;
return mappingSuccess(
Object.freeze({ id: candidate.id, title: candidate.title }),
Object.freeze({ id: value.id, title: value.title }),
);
},
mapProblem(problem, metadata) {
if (metadata.status !== 409) return undefined;
return Object.freeze({
kind: "CONFLICT" as const,
code: problem.code ?? "RESOURCE_CONFLICT",
});
},
}),
} as const);
function scriptedExecutor<WireOutput, Problem>(
outcome: HttpExecutionOutcome<WireOutput, Problem>,
) {
const calls: Array<
Readonly<{
contract: unknown;
input: unknown;
context: unknown;
}>
> = [];
const implementation = async (
contract: unknown,
input: unknown,
context: unknown,
) => {
calls.push(Object.freeze({ contract, input, context }));
return outcome;
};
const execute = vi.fn(implementation) as unknown as
InstalledHttpOperationExecutor["execute"];
return {
executor: Object.freeze({ execute }) satisfies InstalledHttpOperationExecutor,
calls,
execute,
};
}
describe("feature HTTP binding", () => {
it("keeps typed feature input while platform owns route/context execution", async () => {
const execute = vi.fn<InstalledHttpOperationExecutor["execute"]>(
async (_operationId, input, context) => {
expect(input).toEqual({ resourceId: "resource-1" });
expect(context.routeId).toBe("RESOURCE_DETAIL");
return Object.freeze({
kind: "SUCCESS" as const,
value: Object.freeze({ id: "resource-1", title: "Reference" }),
metadata: Object.freeze({ status: 200 }),
effect: "NOT_APPLICABLE" as const,
});
},
);
const binding = createFeatureHttpBinding(
Object.freeze({ execute }),
OPERATIONS,
it("keeps contract-derived input while platform owns route/context execution", async () => {
const scripted = scriptedExecutor<ResourceWire, ResourceProblem>(
Object.freeze({
kind: "SUCCESS" as const,
value: Object.freeze({ id: "resource-1", title: "Reference" }),
metadata: Object.freeze({ status: 200 }),
effect: "NOT_APPLICABLE" as const,
}),
);
const binding = createFeatureHttpBinding(scripted.executor, OPERATIONS);
const result = await binding.execute("LOAD_RESOURCE", {
resourceId: "resource-1",
@@ -63,23 +135,27 @@ describe("feature HTTP binding", () => {
ok: true,
value: { id: "resource-1", title: "Reference" },
});
expect(execute).toHaveBeenCalledTimes(1);
expect(scripted.calls).toEqual([
{
contract: LOAD_RESOURCE_CONTRACT,
input: { resourceId: "resource-1" },
context: { routeId: "RESOURCE_DETAIL" },
},
]);
});
it("normalizes transport failure before it crosses the feature gateway", async () => {
const executor: InstalledHttpOperationExecutor = Object.freeze({
async execute() {
return Object.freeze({
kind: "TRANSPORT_FAILURE" as const,
failure: Object.freeze({
kind: "TIMEOUT" as const,
retryable: true,
}),
effect: "NOT_STARTED" as const,
});
},
});
const binding = createFeatureHttpBinding(executor, OPERATIONS);
const scripted = scriptedExecutor<ResourceWire, ResourceProblem>(
Object.freeze({
kind: "TRANSPORT_FAILURE" as const,
failure: Object.freeze({
kind: "TIMEOUT" as const,
retryable: true,
}),
effect: "NOT_STARTED" as const,
}),
);
const binding = createFeatureHttpBinding(scripted.executor, OPERATIONS);
const result = await binding.execute("LOAD_RESOURCE", {
resourceId: "resource-1",
@@ -92,18 +168,16 @@ describe("feature HTTP binding", () => {
expect(result.error.effect).toBe("NOT_STARTED");
});
it("turns feature mapper rejection into the shared mapping failure", async () => {
const executor: InstalledHttpOperationExecutor = Object.freeze({
async execute() {
return Object.freeze({
kind: "SUCCESS" as const,
value: Object.freeze({ unexpected: true }),
metadata: Object.freeze({ status: 200 }),
effect: "NOT_APPLICABLE" as const,
});
},
});
const binding = createFeatureHttpBinding(executor, OPERATIONS);
it("turns a domain mapper rejection into the shared mapping failure", async () => {
const scripted = scriptedExecutor<ResourceWire, ResourceProblem>(
Object.freeze({
kind: "SUCCESS" as const,
value: Object.freeze({ id: "resource-1", title: "" }),
metadata: Object.freeze({ status: 200 }),
effect: "NOT_APPLICABLE" as const,
}),
);
const binding = createFeatureHttpBinding(scripted.executor, OPERATIONS);
const result = await binding.execute("LOAD_RESOURCE", {
resourceId: "resource-1",
@@ -114,4 +188,29 @@ describe("feature HTTP binding", () => {
expect(result.error.kind).toBe("MAPPING_CONTRACT_VIOLATION");
expect(result.error.code).toBe("MAPPING_INVARIANT_REJECTED");
});
it("lets the feature interpret a typed business problem", async () => {
const scripted = scriptedExecutor<ResourceWire, ResourceProblem>(
Object.freeze({
kind: "PROBLEM" as const,
problem: Object.freeze({ code: "RESOURCE_NAME_EXISTS" }),
metadata: Object.freeze({ status: 409 }),
effect: "NOT_APPLIED" as const,
}),
);
const binding = createFeatureHttpBinding(scripted.executor, OPERATIONS);
const result = await binding.execute("LOAD_RESOURCE", {
resourceId: "resource-1",
});
expect(result).toMatchObject({
ok: false,
error: {
kind: "CONFLICT",
code: "RESOURCE_NAME_EXISTS",
httpStatus: 409,
},
});
});
});