refactor: 프론트 템플릿 리펙토링

This commit is contained in:
donghyeon-ka
2026-09-18 15:16:58 +09:00
parent c10a709f2c
commit 5cc41467ae
80 changed files with 7227 additions and 4672 deletions
@@ -1,195 +1,41 @@
import type { Result } from "../../../application/result.ts";
import type { ApiFailure, FailureKind } from "../../../contracts/errors.ts";
import type { FailureEffectCertainty } from "../../../contracts/errors.ts";
import type { MutationIntent } from "../../../contracts/mutation-intent.ts";
import {
createFailure,
kindForStatus,
} from "../../../contracts/errors.ts";
import type { HttpExecutionOutcome } from "../../../adapters/http/index.ts";
createFeatureHttpBinding,
type InstalledHttpOperationExecutor,
} from "../../../adapters/http/index.ts";
import { createReferenceFeatureInput } from "../application/reference-feature-api.ts";
import {
REFERENCE_FEATURE_ID,
} from "../contracts/reference-feature-contract.ts";
import { mapReferenceOperation } from "../contracts/reference-mapper.ts";
import {
createReferenceHttpGateway,
type RawReferenceHttpExecutor,
type ReferenceHttpRequest,
type ReferenceOperationId,
REFERENCE_HTTP_OPERATIONS,
} from "./reference-http-gateway.ts";
export type InstalledContractOperationExecutor = Readonly<{
execute(
operationId: string,
input: unknown,
context: Readonly<{
routeId: string;
signal?: AbortSignal;
intent?: MutationIntent;
}>,
): Promise<HttpExecutionOutcome<unknown, unknown>>;
}>;
export type InstalledContractOperationExecutor =
InstalledHttpOperationExecutor;
/**
* The installed feature consumes the composed external-contract operation
* registry through one descriptor-driven executor. Legacy ApiOperation/schema
* registries are intentionally absent from this production composition seam.
* Feature-owned composition seam.
*
* The feature contributes typed operation descriptors and its application
* gateway. HTTP lifecycle/error normalization stays in the reusable capability
* binding rather than being repeated by every product feature.
*/
export function createReferenceFeatureInstalledInput(context: Readonly<{
contractOperations: InstalledContractOperationExecutor;
contractOperations: InstalledHttpOperationExecutor;
}>) {
const rawHttp: RawReferenceHttpExecutor = Object.freeze({
async execute(request) {
const operationId = request.operationId;
const input = inputFor(request);
const signal = "signal" in request ? request.signal : undefined;
const intent = "intent" in request ? request.intent : undefined;
const outcome = await context.contractOperations.execute(
operationId,
input,
{
// §7.4. The gateway owns the low-cardinality route identity; losing
// it here is what made every V3 diagnostic unattributable.
routeId: request.routeId,
...(signal === undefined ? {} : { signal }),
...(intent === undefined ? {} : { intent }),
},
);
return projectExecutionOutcome(operationId, outcome);
},
});
const http = createFeatureHttpBinding(
context.contractOperations,
REFERENCE_HTTP_OPERATIONS,
);
return Object.freeze({
featureId: REFERENCE_FEATURE_ID,
input: createReferenceFeatureInput(createReferenceHttpGateway(rawHttp)),
input: createReferenceFeatureInput(createReferenceHttpGateway(http)),
});
}
function inputFor(
request: ReferenceHttpRequest<ReferenceOperationId>,
): unknown {
switch (request.operationId) {
case "LIST_REFERENCE_RESOURCES":
return request.searchParams;
case "CREATE_REFERENCE_RESOURCE":
return request.body;
case "GET_REFERENCE_RESOURCE":
return request.pathParams;
}
}
function projectExecutionOutcome(
operationId: ReferenceOperationId,
outcome: HttpExecutionOutcome<unknown, unknown>,
): Result<unknown, ApiFailure> {
switch (outcome.kind) {
case "SUCCESS": {
const mapped = mapReferenceOperation(operationId, outcome.value);
return mapped.ok
? Object.freeze({ ok: true as const, value: mapped.value })
: failure(
"MAPPING_CONTRACT_VIOLATION",
operationId,
mapped.code,
{ effect: outcome.effect },
);
}
case "PROBLEM":
return failure(
kindForStatus(outcome.metadata.status),
operationId,
"CONTRACT_PROBLEM",
{ httpStatus: outcome.metadata.status, effect: outcome.effect },
);
case "UNAUTHENTICATED":
return failure("AUTH_REQUIRED", operationId, "UNAUTHENTICATED", {
effect: outcome.effect,
});
case "FORBIDDEN":
return failure("FORBIDDEN", operationId, "FORBIDDEN", {
effect: outcome.effect,
});
case "RATE_LIMITED":
return failure("RATE_LIMITED", operationId, "RATE_LIMITED", {
...(outcome.retryAfterMs === undefined
? {}
: { retryAfterMs: outcome.retryAfterMs }),
effect: outcome.effect,
});
case "CANCELLED":
return failure("REQUEST_ABORTED", operationId, "REQUEST_ABORTED", {
effect: outcome.effect,
});
case "AUTH_INTEGRATION_FAILURE":
// §7.7. A configuration or collaborator breach, not a session state, so
// it must not drive the re-authentication surface.
return failure(
"AUTH_INTEGRATION_FAILURE",
operationId,
outcome.reason,
{ effect: outcome.effect },
);
case "TRANSPORT_FAILURE":
return failure(
outcome.failure.kind === "TIMEOUT"
? "REQUEST_TIMEOUT"
: outcome.failure.kind === "ABORTED_BY_SCOPE"
? "SCOPE_GENERATION_CHANGED"
: "NETWORK_UNREACHABLE",
operationId,
outcome.failure.kind,
{ effect: outcome.effect },
);
case "CONTRACT_VIOLATION":
return failure(
failureKindForViolation(outcome.violation.kind),
operationId,
outcome.violation.kind,
{ effect: outcome.effect },
);
}
}
function failureKindForViolation(
violation: Extract<
HttpExecutionOutcome<unknown, unknown>,
{ kind: "CONTRACT_VIOLATION" }
>["violation"]["kind"],
): FailureKind {
switch (violation) {
case "CONTENT_TYPE_MISMATCH":
return "CONTENT_TYPE_MISMATCH";
case "RESPONSE_TOO_LARGE":
return "RESPONSE_BODY_LIMIT";
case "UTF8_INVALID":
case "JSON_INVALID":
return "MALFORMED_JSON";
case "MAPPING_CONTRACT_VIOLATION":
return "MAPPING_CONTRACT_VIOLATION";
case "SCOPE_FENCED":
return "SCOPE_GENERATION_CHANGED";
case "SUCCESS_SCHEMA_INVALID":
case "PROBLEM_SCHEMA_INVALID":
case "VALIDATOR_RUNTIME_FAILURE":
return "SCHEMA_MISMATCH";
default:
return "ENVELOPE_MISMATCH";
}
}
function failure(
kind: FailureKind,
operationId: string,
code: string,
details: Readonly<{
httpStatus?: number;
retryAfterMs?: number;
effect?: FailureEffectCertainty;
}> = {},
): Result<never, ApiFailure> {
return Object.freeze({
ok: false as const,
error: createFailure(kind, operationId, 0, { code, ...details }),
});
}
export const REFERENCE_FEATURE_ADAPTER_CONTRIBUTION = Object.freeze({
featureId: REFERENCE_FEATURE_ID,
createInput: createReferenceFeatureInstalledInput,
});
@@ -1,160 +1,65 @@
import type { Result } from "../../../application/result.ts";
import {
createFailure,
type ApiFailure,
} from "../../../contracts/errors.ts";
defineFeatureHttpOperation,
type FeatureHttpBinding,
} from "../../../adapters/http/index.ts";
import type {
ReferenceCreateCommand,
ReferenceGateway,
ReferenceListFilters,
} from "../application/reference-feature-api.ts";
import type { ReferenceResource } from "../domain/reference-resource.ts";
import type { MutationIntent } from "../../../contracts/mutation-intent.ts";
import {
mapReferenceResourceListPayload,
mapReferenceResourcePayload,
} from "../contracts/reference-mapper.ts";
type ReferenceOperationMap = Readonly<{
LIST_REFERENCE_RESOURCES: Readonly<{
request: Readonly<{
operationId: "LIST_REFERENCE_RESOURCES";
routeId: "REFERENCE_RESOURCE_LIST";
searchParams: ReferenceListFilters;
signal?: AbortSignal;
}>;
value: readonly ReferenceResource[];
}>;
CREATE_REFERENCE_RESOURCE: Readonly<{
request: Readonly<{
operationId: "CREATE_REFERENCE_RESOURCE";
routeId: "REFERENCE_RESOURCE_LIST";
body: ReferenceCreateCommand;
signal?: AbortSignal;
intent?: MutationIntent;
}>;
value: ReferenceResource;
}>;
GET_REFERENCE_RESOURCE: Readonly<{
request: Readonly<{
operationId: "GET_REFERENCE_RESOURCE";
routeId: "REFERENCE_RESOURCE_DETAIL";
pathParams: Readonly<{ resourceId: string }>;
signal?: AbortSignal;
}>;
value: ReferenceResource;
}>;
}>;
export const REFERENCE_HTTP_OPERATIONS = Object.freeze({
LIST_REFERENCE_RESOURCES: defineFeatureHttpOperation<
ReferenceListFilters,
readonly ReferenceResource[]
>({
operationId: "LIST_REFERENCE_RESOURCES",
routeId: "REFERENCE_RESOURCE_LIST",
mapSuccess: mapReferenceResourceListPayload,
}),
CREATE_REFERENCE_RESOURCE: defineFeatureHttpOperation<
ReferenceCreateCommand,
ReferenceResource
>({
operationId: "CREATE_REFERENCE_RESOURCE",
routeId: "REFERENCE_RESOURCE_LIST",
mapSuccess: mapReferenceResourcePayload,
}),
GET_REFERENCE_RESOURCE: defineFeatureHttpOperation<
Readonly<{ resourceId: string }>,
ReferenceResource
>({
operationId: "GET_REFERENCE_RESOURCE",
routeId: "REFERENCE_RESOURCE_DETAIL",
mapSuccess: mapReferenceResourcePayload,
}),
} as const);
export type ReferenceOperationId = keyof ReferenceOperationMap;
export type ReferenceHttpRequest<
OperationId extends ReferenceOperationId,
> = ReferenceOperationMap[OperationId]["request"];
export type ReferenceHttpResult<
OperationId extends ReferenceOperationId,
> = Result<ReferenceOperationMap[OperationId]["value"], ApiFailure>;
export type RawReferenceHttpExecutor = Readonly<{
execute(
request: ReferenceHttpRequest<ReferenceOperationId>,
): Promise<Result<unknown, ApiFailure>>;
}>;
export type ReferenceHttpBinding = FeatureHttpBinding<
typeof REFERENCE_HTTP_OPERATIONS
>;
export function createReferenceHttpGateway(
http: RawReferenceHttpExecutor,
http: ReferenceHttpBinding,
): ReferenceGateway {
return Object.freeze({
async list(
filters: ReferenceListFilters,
context?: Readonly<{ signal?: AbortSignal }>,
) {
const result = await http.execute({
operationId: "LIST_REFERENCE_RESOURCES",
routeId: "REFERENCE_RESOURCE_LIST",
searchParams: filters,
signal: context?.signal,
});
return projectListResult(result);
list(filters, context) {
return http.execute("LIST_REFERENCE_RESOURCES", filters, context);
},
async create(
command: ReferenceCreateCommand,
context?: Readonly<{
signal?: AbortSignal;
intent?: MutationIntent;
}>,
) {
const result = await http.execute({
operationId: "CREATE_REFERENCE_RESOURCE",
routeId: "REFERENCE_RESOURCE_LIST",
body: command,
signal: context?.signal,
intent: context?.intent,
});
return projectResourceResult("CREATE_REFERENCE_RESOURCE", result);
create(command, context) {
return http.execute("CREATE_REFERENCE_RESOURCE", command, context);
},
async get(
resourceId: string,
context?: Readonly<{ signal?: AbortSignal }>,
) {
const result = await http.execute({
operationId: "GET_REFERENCE_RESOURCE",
routeId: "REFERENCE_RESOURCE_DETAIL",
pathParams: { resourceId },
signal: context?.signal,
});
return projectResourceResult("GET_REFERENCE_RESOURCE", result);
get(resourceId, context) {
return http.execute(
"GET_REFERENCE_RESOURCE",
Object.freeze({ resourceId }),
context,
);
},
});
}
function projectListResult(
result: Result<unknown, ApiFailure>,
): ReferenceHttpResult<"LIST_REFERENCE_RESOURCES"> {
if (!result.ok) return result;
if (isReferenceResourceList(result.value)) {
return { ok: true, value: result.value };
}
return typedResultFailure("LIST_REFERENCE_RESOURCES");
}
function projectResourceResult(
operationId:
| "CREATE_REFERENCE_RESOURCE"
| "GET_REFERENCE_RESOURCE",
result: Result<unknown, ApiFailure>,
): Result<ReferenceResource, ApiFailure> {
if (!result.ok) return result;
if (isReferenceResource(result.value)) {
return { ok: true, value: result.value };
}
return typedResultFailure(operationId);
}
function typedResultFailure(operationId: ReferenceOperationId) {
return {
ok: false as const,
error: createFailure(
"MAPPING_CONTRACT_VIOLATION",
operationId,
0,
{ code: "BOUND_RESULT_TYPE_MISMATCH" },
),
};
}
function isReferenceResourceList(
value: unknown,
): value is readonly ReferenceResource[] {
return Array.isArray(value) && value.every(isReferenceResource);
}
function isReferenceResource(value: unknown): value is ReferenceResource {
if (!value || typeof value !== "object") return false;
const candidate = value as Readonly<Record<string, unknown>>;
return (
typeof candidate.id === "string" &&
candidate.id.length > 0 &&
typeof candidate.displayName === "string" &&
candidate.displayName.length > 0 &&
(candidate.createdAt === null ||
typeof candidate.createdAt === "string")
);
}