feat: 기능 추가 과정중

This commit is contained in:
donghyeon-ka
2026-07-30 15:58:20 +09:00
parent d3ef801fe6
commit 6c52cdb916
648 changed files with 126325 additions and 6680 deletions
@@ -1,30 +1,45 @@
import { createReferenceFeatureInput } from "../application/reference-feature-api.js";
import type { ApiOperation } from "../../../contracts/api-operations.ts";
import { createReferenceFeatureInput } from "../application/reference-feature-api.ts";
import {
REFERENCE_FEATURE_CONTRACT,
REFERENCE_FEATURE_ID,
} from "../contracts/reference-feature-contract.js";
import { mapReferenceOperation } from "../contracts/reference-mapper.js";
} from "../contracts/reference-feature-contract.ts";
import {
validateReferencePayload,
validateReferenceRequest,
} from "../contracts/reference-schemas.js";
import { createReferenceHttpGateway } from "./reference-http-gateway.js";
mapWithBoundaryRegistry,
type MappingResult,
} from "../../../contracts/boundary-mapper.ts";
import { validateWithRuntimeSchemaRegistry } from "../../../contracts/schema-registry.ts";
import {
createReferenceHttpGateway,
type RawReferenceHttpExecutor,
} from "./reference-http-gateway.ts";
type HttpContract = Readonly<{
getOperation(operationId: string): unknown;
validatePayload: typeof validateReferencePayload;
validateRequest: typeof validateReferenceRequest;
mapPayload: typeof mapReferenceOperation;
getOperation(operationId: string): ApiOperation;
validatePayload(schemaId: string, value: unknown): ReturnType<
typeof validateWithRuntimeSchemaRegistry
>;
validateRequest(schemaId: string, value: unknown): ReturnType<
typeof validateWithRuntimeSchemaRegistry
>;
validatePath(schemaId: string, value: unknown): ReturnType<
typeof validateWithRuntimeSchemaRegistry
>;
mapPayload(operationId: string, payload: unknown): MappingResult<unknown>;
}>;
type HttpExecutor = Parameters<typeof createReferenceHttpGateway>[0];
type HttpExecutor = RawReferenceHttpExecutor;
export function createReferenceFeatureInstalledInput(context: Readonly<{
createHttpClient(contract: HttpContract): HttpExecutor;
}>) {
const operations =
REFERENCE_FEATURE_CONTRACT.apiOperations as Readonly<Record<string, unknown>>;
const http = context.createHttpClient({
REFERENCE_FEATURE_CONTRACT.apiOperations as Readonly<Record<string, ApiOperation>>;
const schemas = REFERENCE_FEATURE_CONTRACT.runtimeSchemas;
const mappers = REFERENCE_FEATURE_CONTRACT.mappers;
const validate = (schemaId: string, value: unknown) =>
validateWithRuntimeSchemaRegistry(schemaId, value, schemas);
const rawHttp = context.createHttpClient({
getOperation(operationId) {
const operation = operations[operationId];
if (!operation) {
@@ -32,12 +47,19 @@ export function createReferenceFeatureInstalledInput(context: Readonly<{
}
return operation;
},
validatePayload: validateReferencePayload,
validateRequest: validateReferenceRequest,
mapPayload: mapReferenceOperation,
validatePayload: validate,
validateRequest: validate,
validatePath: validate,
mapPayload(operationId, payload) {
const operation = operations[operationId];
if (!operation?.mapperId) {
return { ok: false, code: "MAPPING_INVARIANT_REJECTED" };
}
return mapWithBoundaryRegistry(operation.mapperId, payload, mappers);
},
});
return Object.freeze({
featureId: REFERENCE_FEATURE_ID,
input: createReferenceFeatureInput(createReferenceHttpGateway(http)),
input: createReferenceFeatureInput(createReferenceHttpGateway(rawHttp)),
});
}
@@ -1,29 +1,62 @@
import type { ApiFailure } from "../../../contracts/errors.js";
import type { Result } from "../../../application/result.ts";
import {
createFailure,
type ApiFailure,
} from "../../../contracts/errors.ts";
import type {
ReferenceCreateCommand,
ReferenceGateway,
ReferenceListFilters,
} from "../application/reference-feature-api.js";
import type { ReferenceResource } from "../domain/reference-resource.js";
} from "../application/reference-feature-api.ts";
import type { ReferenceResource } from "../domain/reference-resource.ts";
type HttpResult =
| Readonly<{ ok: true; value: unknown }>
| Readonly<{ ok: false; error: ApiFailure }>;
type HttpExecutor = Readonly<{
execute(
type ReferenceOperationMap = Readonly<{
LIST_REFERENCE_RESOURCES: Readonly<{
request: Readonly<{
operationId: string;
routeId: string;
pathParams?: Record<string, string | number>;
searchParams?: unknown;
body?: unknown;
operationId: "LIST_REFERENCE_RESOURCES";
routeId: "REFERENCE_RESOURCE_LIST";
searchParams: ReferenceListFilters;
signal?: AbortSignal;
}>,
): Promise<HttpResult>;
}>;
value: readonly ReferenceResource[];
}>;
CREATE_REFERENCE_RESOURCE: Readonly<{
request: Readonly<{
operationId: "CREATE_REFERENCE_RESOURCE";
routeId: "REFERENCE_RESOURCE_LIST";
body: ReferenceCreateCommand;
}>;
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 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 function createReferenceHttpGateway(
http: HttpExecutor,
http: RawReferenceHttpExecutor,
): ReferenceGateway {
return Object.freeze({
async list(
@@ -36,22 +69,15 @@ export function createReferenceHttpGateway(
searchParams: filters,
signal: context?.signal,
});
return result.ok
? {
ok: true as const,
value: result.value as readonly ReferenceResource[],
}
: result;
return projectListResult(result);
},
async create(command: Readonly<{ name: string; note?: string }>) {
async create(command: ReferenceCreateCommand) {
const result = await http.execute({
operationId: "CREATE_REFERENCE_RESOURCE",
routeId: "REFERENCE_RESOURCE_LIST",
body: command,
});
return result.ok
? { ok: true as const, value: result.value as ReferenceResource }
: result;
return projectResourceResult("CREATE_REFERENCE_RESOURCE", result);
},
async get(
resourceId: string,
@@ -63,9 +89,61 @@ export function createReferenceHttpGateway(
pathParams: { resourceId },
signal: context?.signal,
});
return result.ok
? { ok: true as const, value: result.value as ReferenceResource }
: result;
return projectResourceResult("GET_REFERENCE_RESOURCE", result);
},
});
}
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")
);
}