feat: 기능 추가 과정중
This commit is contained in:
@@ -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")
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user