refactor: 프론트엔드 리펙토링
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
type InstalledBoundaryMapper,
|
||||
type MappingResult,
|
||||
} from "../../../contracts/boundary-mapper.ts";
|
||||
import type { ReferenceResourceDto } from "./reference-schemas.ts";
|
||||
|
||||
export type ReferenceResourceView = Readonly<{
|
||||
resourceId: string;
|
||||
@@ -16,22 +17,19 @@ export type ReferenceResourceView = Readonly<{
|
||||
optimistic?: boolean;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Operation-specific mapper. The HTTP contract has already validated the wire
|
||||
* shape, so this mapper owns only wire -> domain construction.
|
||||
*/
|
||||
export function mapReferenceResourcePayload(
|
||||
value: unknown,
|
||||
dto: ReferenceResourceDto,
|
||||
): MappingResult<ReferenceResource> {
|
||||
if (!value || typeof value !== "object") {
|
||||
return mappingFailure("MAPPING_INVARIANT_REJECTED");
|
||||
}
|
||||
const dto = value as Record<string, unknown>;
|
||||
if (typeof dto.id !== "string" || typeof dto.name !== "string") {
|
||||
return mappingFailure("MAPPING_INVARIANT_REJECTED");
|
||||
}
|
||||
try {
|
||||
return mappingSuccess(
|
||||
createReferenceResource({
|
||||
id: dto.id,
|
||||
displayName: dto.name,
|
||||
createdAt: typeof dto.createdAt === "string" ? dto.createdAt : null,
|
||||
createdAt: dto.createdAt ?? null,
|
||||
}),
|
||||
);
|
||||
} catch {
|
||||
@@ -40,14 +38,8 @@ export function mapReferenceResourcePayload(
|
||||
}
|
||||
|
||||
export function mapReferenceResourceListPayload(
|
||||
payload: unknown,
|
||||
payload: readonly ReferenceResourceDto[],
|
||||
): MappingResult<readonly ReferenceResource[]> {
|
||||
if (!Array.isArray(payload)) {
|
||||
return mappingFailure("MAPPING_INVARIANT_REJECTED");
|
||||
}
|
||||
if (payload.length > 100) {
|
||||
return mappingFailure("OUTPUT_LIMIT_EXCEEDED");
|
||||
}
|
||||
const output: ReferenceResource[] = [];
|
||||
for (const item of payload) {
|
||||
const mapped = mapReferenceResourcePayload(item);
|
||||
@@ -57,22 +49,58 @@ export function mapReferenceResourceListPayload(
|
||||
return mappingSuccess(Object.freeze(output));
|
||||
}
|
||||
|
||||
function isReferenceResourceDto(value: unknown): value is ReferenceResourceDto {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
||||
const dto = value as Record<string, unknown>;
|
||||
return (
|
||||
typeof dto.id === "string" &&
|
||||
typeof dto.name === "string" &&
|
||||
(dto.createdAt === undefined || typeof dto.createdAt === "string")
|
||||
);
|
||||
}
|
||||
|
||||
function mapUnknownReferenceResourcePayload(
|
||||
value: unknown,
|
||||
): MappingResult<ReferenceResource> {
|
||||
return isReferenceResourceDto(value)
|
||||
? mapReferenceResourcePayload(value)
|
||||
: mappingFailure("MAPPING_INVARIANT_REJECTED");
|
||||
}
|
||||
|
||||
function mapUnknownReferenceResourceListPayload(
|
||||
value: unknown,
|
||||
): MappingResult<readonly ReferenceResource[]> {
|
||||
if (
|
||||
!Array.isArray(value) ||
|
||||
value.length > 100 ||
|
||||
!value.every(isReferenceResourceDto)
|
||||
) {
|
||||
return mappingFailure(
|
||||
Array.isArray(value) && value.length > 100
|
||||
? "OUTPUT_LIMIT_EXCEEDED"
|
||||
: "MAPPING_INVARIANT_REJECTED",
|
||||
);
|
||||
}
|
||||
return mapReferenceResourceListPayload(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registry-facing compatibility projection. Feature adapters should prefer the
|
||||
* operation-specific mapper functions above so their result type is exact.
|
||||
* Registry-facing compatibility projection. The registry is intentionally
|
||||
* untyped, so its adapter performs the defensive shape check. Feature HTTP
|
||||
* bindings use the typed operation-specific mappers above.
|
||||
*/
|
||||
export function mapReferenceOperation(
|
||||
operationId: string,
|
||||
payload: unknown,
|
||||
): MappingResult<ReferenceResource | readonly ReferenceResource[]> {
|
||||
if (operationId === "LIST_REFERENCE_RESOURCES") {
|
||||
return mapReferenceResourceListPayload(payload);
|
||||
return mapUnknownReferenceResourceListPayload(payload);
|
||||
}
|
||||
if (
|
||||
operationId === "CREATE_REFERENCE_RESOURCE" ||
|
||||
operationId === "GET_REFERENCE_RESOURCE"
|
||||
) {
|
||||
return mapReferenceResourcePayload(payload);
|
||||
return mapUnknownReferenceResourcePayload(payload);
|
||||
}
|
||||
return mappingFailure("MAPPING_INVARIANT_REJECTED");
|
||||
}
|
||||
@@ -85,7 +113,7 @@ export const REFERENCE_BOUNDARY_MAPPERS = Object.freeze({
|
||||
outputContractId: "ReferenceResourceList",
|
||||
owner: "feature-frontend-reference-feature-vertical-slice",
|
||||
maxOutputItems: 100,
|
||||
map: mapReferenceResourceListPayload,
|
||||
map: mapUnknownReferenceResourceListPayload,
|
||||
}),
|
||||
ReferenceResourceMapper: Object.freeze({
|
||||
mapperId: "ReferenceResourceMapper",
|
||||
@@ -94,7 +122,7 @@ export const REFERENCE_BOUNDARY_MAPPERS = Object.freeze({
|
||||
outputContractId: "ReferenceResource",
|
||||
owner: "feature-frontend-reference-feature-vertical-slice",
|
||||
maxOutputItems: 1,
|
||||
map: mapReferenceResourcePayload,
|
||||
map: mapUnknownReferenceResourcePayload,
|
||||
}),
|
||||
} satisfies Readonly<Record<string, InstalledBoundaryMapper>>);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user