138 lines
4.1 KiB
TypeScript
138 lines
4.1 KiB
TypeScript
import {
|
|
createReferenceResource,
|
|
type ReferenceResource,
|
|
} from "../domain/reference-resource.ts";
|
|
import {
|
|
mappingFailure,
|
|
mappingSuccess,
|
|
type InstalledBoundaryMapper,
|
|
type MappingResult,
|
|
} from "../../../contracts/boundary-mapper.ts";
|
|
import type { ReferenceResourceDto } from "./reference-schemas.ts";
|
|
|
|
export type ReferenceResourceView = Readonly<{
|
|
resourceId: string;
|
|
title: string;
|
|
createdAt: string | null;
|
|
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(
|
|
dto: ReferenceResourceDto,
|
|
): MappingResult<ReferenceResource> {
|
|
try {
|
|
return mappingSuccess(
|
|
createReferenceResource({
|
|
id: dto.id,
|
|
displayName: dto.name,
|
|
createdAt: dto.createdAt ?? null,
|
|
}),
|
|
);
|
|
} catch {
|
|
return mappingFailure("MAPPING_INVARIANT_REJECTED");
|
|
}
|
|
}
|
|
|
|
export function mapReferenceResourceListPayload(
|
|
payload: readonly ReferenceResourceDto[],
|
|
): MappingResult<readonly ReferenceResource[]> {
|
|
const output: ReferenceResource[] = [];
|
|
for (const item of payload) {
|
|
const mapped = mapReferenceResourcePayload(item);
|
|
if (!mapped.ok) return mapped;
|
|
output.push(mapped.value);
|
|
}
|
|
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. 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 mapUnknownReferenceResourceListPayload(payload);
|
|
}
|
|
if (
|
|
operationId === "CREATE_REFERENCE_RESOURCE" ||
|
|
operationId === "GET_REFERENCE_RESOURCE"
|
|
) {
|
|
return mapUnknownReferenceResourcePayload(payload);
|
|
}
|
|
return mappingFailure("MAPPING_INVARIANT_REJECTED");
|
|
}
|
|
|
|
export const REFERENCE_BOUNDARY_MAPPERS = Object.freeze({
|
|
ReferenceResourceListMapper: Object.freeze({
|
|
mapperId: "ReferenceResourceListMapper",
|
|
mapperVersion: 1,
|
|
inputSchemaId: "ReferenceResourceListPayload",
|
|
outputContractId: "ReferenceResourceList",
|
|
owner: "feature-frontend-reference-feature-vertical-slice",
|
|
maxOutputItems: 100,
|
|
map: mapUnknownReferenceResourceListPayload,
|
|
}),
|
|
ReferenceResourceMapper: Object.freeze({
|
|
mapperId: "ReferenceResourceMapper",
|
|
mapperVersion: 1,
|
|
inputSchemaId: "ReferenceResourcePayload",
|
|
outputContractId: "ReferenceResource",
|
|
owner: "feature-frontend-reference-feature-vertical-slice",
|
|
maxOutputItems: 1,
|
|
map: mapUnknownReferenceResourcePayload,
|
|
}),
|
|
} satisfies Readonly<Record<string, InstalledBoundaryMapper>>);
|
|
|
|
export function toReferenceView(
|
|
resource: ReferenceResource,
|
|
): ReferenceResourceView {
|
|
return Object.freeze({
|
|
resourceId: resource.id,
|
|
title: resource.displayName,
|
|
createdAt: resource.createdAt,
|
|
});
|
|
}
|