refactor: 리펙토링

This commit is contained in:
DongHyeonka
2026-08-01 19:39:59 +09:00
parent 9c959ea2a5
commit c6da03369c
171 changed files with 20329 additions and 782 deletions
@@ -1,65 +1,174 @@
import type { ApiOperation } from "../../../contracts/api-operations.ts";
import type { Result } from "../../../application/result.ts";
import type { ApiFailure, FailureKind } from "../../../contracts/errors.ts";
import type { FailureEffectCertainty } from "../../../contracts/errors.ts";
import {
createFailure,
kindForStatus,
} from "../../../contracts/errors.ts";
import type { HttpExecutionOutcome } from "../../../adapters/http/http-execution-v3.ts";
import { createReferenceFeatureInput } from "../application/reference-feature-api.ts";
import {
REFERENCE_FEATURE_CONTRACT,
REFERENCE_FEATURE_ID,
} from "../contracts/reference-feature-contract.ts";
import {
mapWithBoundaryRegistry,
type MappingResult,
} from "../../../contracts/boundary-mapper.ts";
import { validateWithRuntimeSchemaRegistry } from "../../../contracts/schema-registry.ts";
import { mapReferenceOperation } from "../contracts/reference-mapper.ts";
import {
createReferenceHttpGateway,
type RawReferenceHttpExecutor,
type ReferenceHttpRequest,
type ReferenceOperationId,
} from "./reference-http-gateway.ts";
type HttpContract = Readonly<{
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>;
export type InstalledContractOperationExecutor = Readonly<{
execute(
operationId: string,
input: unknown,
context?: Readonly<{ signal?: AbortSignal }>,
): Promise<HttpExecutionOutcome<unknown, unknown>>;
}>;
type HttpExecutor = RawReferenceHttpExecutor;
/**
* 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.
*/
export function createReferenceFeatureInstalledInput(context: Readonly<{
createHttpClient(contract: HttpContract): HttpExecutor;
contractOperations: InstalledContractOperationExecutor;
}>) {
const operations =
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) {
throw new Error(`Unknown reference operation: ${operationId}`);
}
return operation;
},
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);
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 outcome = await context.contractOperations.execute(
operationId,
input,
signal === undefined ? {} : { signal },
);
return projectExecutionOutcome(operationId, outcome);
},
});
return Object.freeze({
featureId: REFERENCE_FEATURE_ID,
input: createReferenceFeatureInput(createReferenceHttpGateway(rawHttp)),
});
}
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 "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 }),
});
}
@@ -25,6 +25,7 @@ type ReferenceOperationMap = Readonly<{
operationId: "CREATE_REFERENCE_RESOURCE";
routeId: "REFERENCE_RESOURCE_LIST";
body: ReferenceCreateCommand;
signal?: AbortSignal;
}>;
value: ReferenceResource;
}>;
@@ -71,11 +72,15 @@ export function createReferenceHttpGateway(
});
return projectListResult(result);
},
async create(command: ReferenceCreateCommand) {
async create(
command: ReferenceCreateCommand,
context?: Readonly<{ signal?: AbortSignal }>,
) {
const result = await http.execute({
operationId: "CREATE_REFERENCE_RESOURCE",
routeId: "REFERENCE_RESOURCE_LIST",
body: command,
signal: context?.signal,
});
return projectResourceResult("CREATE_REFERENCE_RESOURCE", result);
},
@@ -26,6 +26,7 @@ export type ReferenceFeatureInput = Readonly<{
): Promise<ReferenceResult<readonly ReferenceResourceView[]>>;
createResource(
command: ReferenceCreateCommand,
context?: Readonly<{ signal?: AbortSignal }>,
): Promise<ReferenceResult<ReferenceResourceView>>;
getResource(
resourceId: string,
@@ -46,6 +47,7 @@ export type ReferenceGateway = Readonly<{
): Promise<ReferenceResult<readonly ReferenceResource[]>>;
create(
command: ReferenceCreateCommand,
context?: Readonly<{ signal?: AbortSignal }>,
): Promise<ReferenceResult<ReferenceResource>>;
get(
resourceId: string,
@@ -66,8 +68,8 @@ export function createReferenceFeatureInput(
}
: result;
},
async createResource(command) {
const result = await gateway.create(command);
async createResource(command, context) {
const result = await gateway.create(command, context);
return result.ok
? { ok: true as const, value: toReferenceView(result.value) }
: result;
@@ -0,0 +1,255 @@
import { z } from "zod";
import type {
CommandEffectDescriptor,
HttpRequestProjection,
InstalledContractContribution,
InstalledHttpContract,
RuntimeValidator,
} from "../../../contracts/external-contract-runtime.ts";
import { REFERENCE_FEATURE_ID } from "./reference-feature-contract.ts";
import {
referenceResourceListQuerySchema,
referenceResourceParamsSchema,
} from "./reference-schemas.ts";
/**
* §4.8. The single `TEMPLATE_FIXTURE` contribution. It keeps the reference
* HTTP vertical executable as deterministic template data and is excluded from
* `contractSet`. A product feature MUST instead pin an external package and
* import it from its own `contracts/<service>-contract-contribution.ts`.
*/
function zodValidator<T>(
schemaId: string,
schema: z.ZodType<T>,
): RuntimeValidator<T> {
return Object.freeze({
schemaId,
safeParse(value: unknown) {
const result = schema.safeParse(value);
if (result.success) {
return Object.freeze({
success: true as const,
data: structuredClone(result.data),
});
}
return Object.freeze({
success: false as const,
issues: Object.freeze(
result.error.issues.map((issue) =>
Object.freeze({
path: Object.freeze(
issue.path.map((segment): string | number =>
typeof segment === "number" ? segment : String(segment),
),
),
code: String(issue.code),
}),
),
),
});
},
});
}
const referenceResourceDto = z
.object({
id: z.string().min(1).max(120),
name: z.string().min(1).max(240),
createdAt: z.string().min(1).optional(),
})
.strip();
const problemSchema = z
.object({
type: z.string().min(1).max(512),
title: z.string().min(1).max(240),
status: z.int().min(100).max(599),
code: z.string().min(1).max(120).optional(),
})
.strip();
export type ReferenceProblem = z.output<typeof problemSchema>;
const PROBLEM_VALIDATOR = zodValidator("ReferenceProblem", problemSchema);
const createCommandSchema = z
.object({
name: z.string().trim().min(1).max(120),
note: z.string().trim().max(500).optional(),
})
.strict();
/**
* Fixture-side classifier standing in for the package-provided pure bounded
* classifier. A status the fixture does not describe stays `MAYBE_APPLIED`.
*/
const CREATE_EFFECT: CommandEffectDescriptor<ReferenceProblem> = Object.freeze({
successEffect: "APPLIED_CONFIRMED" as const,
classifyProblem({ status }: Readonly<{ status: number; problem: ReferenceProblem }>) {
if (status === 400 || status === 409 || status === 422) return "NOT_APPLIED";
return "MAYBE_APPLIED";
},
});
const LIST_REFERENCE_RESOURCES: InstalledHttpContract<
z.output<typeof referenceResourceListQuerySchema>,
readonly z.output<typeof referenceResourceDto>[],
ReferenceProblem
> = Object.freeze({
contract: Object.freeze({
operationId: "LIST_REFERENCE_RESOURCES",
method: "GET" as const,
pathTemplate: "/api/reference-resources",
inputValidator: zodValidator(
"ReferenceResourceListQuery",
referenceResourceListQuerySchema,
),
outputValidator: zodValidator(
"ReferenceResourceListPayload",
z.array(referenceResourceDto).max(100),
),
problemValidator: PROBLEM_VALIDATOR,
acceptedStatuses: Object.freeze([200]),
emptyBodyStatuses: Object.freeze([]),
retrySemantics: "SAFE" as const,
requestBody: "NONE" as const,
responseBody: "REQUIRED_JSON" as const,
commandRecovery: null,
commandEffect: null,
projectRequest(input: z.output<typeof referenceResourceListQuerySchema>) {
const entries: (readonly [string, string])[] = [];
if (input.cursor !== undefined) entries.push(["cursor", input.cursor]);
entries.push(["limit", String(input.limit)]);
for (const tag of input.tags ?? []) entries.push(["tags", tag]);
return Object.freeze({
pathValues: Object.freeze({}),
queryEntries: Object.freeze(entries),
body: null,
});
},
}),
frontend: Object.freeze({
policyId: "REFERENCE_LIST_V1",
requestByteLimit: 0,
responseByteLimit: 262_144,
totalDeadlineMs: 10_000,
retryBudget: 2 as const,
authProfileId: "REFERENCE_EXTERNAL_BEARER",
diagnosticsOperation: "reference.list",
}),
});
const GET_REFERENCE_RESOURCE: InstalledHttpContract<
z.output<typeof referenceResourceParamsSchema>,
z.output<typeof referenceResourceDto>,
ReferenceProblem
> = Object.freeze({
contract: Object.freeze({
operationId: "GET_REFERENCE_RESOURCE",
method: "GET" as const,
pathTemplate: "/api/reference-resources/{resourceId}",
inputValidator: zodValidator(
"ReferenceResourceParams",
referenceResourceParamsSchema,
),
outputValidator: zodValidator(
"ReferenceResourcePayload",
referenceResourceDto,
),
problemValidator: PROBLEM_VALIDATOR,
acceptedStatuses: Object.freeze([200]),
emptyBodyStatuses: Object.freeze([]),
retrySemantics: "SAFE" as const,
requestBody: "NONE" as const,
responseBody: "REQUIRED_JSON" as const,
commandRecovery: null,
commandEffect: null,
projectRequest(input: z.output<typeof referenceResourceParamsSchema>) {
return Object.freeze({
pathValues: Object.freeze({ resourceId: input.resourceId }),
queryEntries: Object.freeze([]),
body: null,
});
},
}),
frontend: Object.freeze({
policyId: "REFERENCE_DETAIL_V1",
requestByteLimit: 0,
responseByteLimit: 32_768,
totalDeadlineMs: 10_000,
retryBudget: 2 as const,
authProfileId: "REFERENCE_EXTERNAL_BEARER",
diagnosticsOperation: "reference.detail",
}),
});
const CREATE_REFERENCE_RESOURCE: InstalledHttpContract<
z.output<typeof createCommandSchema>,
z.output<typeof referenceResourceDto>,
ReferenceProblem
> = Object.freeze({
contract: Object.freeze({
operationId: "CREATE_REFERENCE_RESOURCE",
method: "POST" as const,
pathTemplate: "/api/reference-resources",
inputValidator: zodValidator(
"CreateReferenceResourceCommand",
createCommandSchema,
),
outputValidator: zodValidator(
"ReferenceResourcePayload",
referenceResourceDto,
),
problemValidator: PROBLEM_VALIDATOR,
acceptedStatuses: Object.freeze([200, 201]),
emptyBodyStatuses: Object.freeze([]),
retrySemantics: "KEYED" as const,
requestBody: "JSON" as const,
responseBody: "REQUIRED_JSON" as const,
commandRecovery: Object.freeze({
mode: "IDEMPOTENCY_REPLAY" as const,
operationIdentityField: "idempotencyKey",
}),
commandEffect: CREATE_EFFECT,
projectRequest(input: z.output<typeof createCommandSchema>) {
return Object.freeze({
pathValues: Object.freeze({}),
queryEntries: Object.freeze([]),
body: Object.freeze({ ...input }),
});
},
}),
frontend: Object.freeze({
policyId: "REFERENCE_CREATE_V1",
requestByteLimit: 32_768,
responseByteLimit: 32_768,
totalDeadlineMs: 10_000,
/**
* §8.3. A KEYED command has no automatic retry after a dispatched attempt
* lost its response; the fixture therefore declares a zero budget rather
* than relying on a runtime special case.
*/
retryBudget: 0 as const,
authProfileId: "REFERENCE_EXTERNAL_BEARER",
diagnosticsOperation: "reference.create",
}),
});
export const REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION: InstalledContractContribution =
Object.freeze({
contributionId: "reference-feature-http-v1",
featureId: REFERENCE_FEATURE_ID,
source: Object.freeze({
kind: "TEMPLATE_FIXTURE" as const,
fixtureId: "REFERENCE_FEATURE_V1" as const,
revision: 1 as const,
}),
http: Object.freeze([
LIST_REFERENCE_RESOURCES,
GET_REFERENCE_RESOURCE,
CREATE_REFERENCE_RESOURCE,
]) as readonly InstalledHttpContract<unknown, unknown, unknown>[],
events: Object.freeze([]),
});
@@ -84,7 +84,7 @@ export const REFERENCE_FEATURE_CONTRACT = Object.freeze({
path: "/examples/reference-resources",
paramsSchema: null,
searchSchema: "ReferenceResourceListQuery",
access: "integration-defined",
access: "session-required",
loadingSurface: "reference-resource-list",
errorSurface: "feature-boundary",
chunkId: "route-reference-resources",
@@ -97,7 +97,7 @@ export const REFERENCE_FEATURE_CONTRACT = Object.freeze({
path: "/examples/reference-resources/:resourceId",
paramsSchema: "ReferenceResourceParams",
searchSchema: null,
access: "integration-defined",
access: "session-required",
loadingSurface: "reference-resource-detail",
errorSurface: "feature-boundary",
chunkId: "route-reference-resource-detail",
@@ -110,7 +110,7 @@ export const REFERENCE_FEATURE_CONTRACT = Object.freeze({
path: "/examples/reference-resources/new",
paramsSchema: null,
searchSchema: null,
access: "integration-defined",
access: "session-required",
loadingSurface: "reference-resource-form",
errorSurface: "feature-boundary",
chunkId: "route-reference-resource-form",
@@ -123,7 +123,7 @@ export const REFERENCE_FEATURE_CONTRACT = Object.freeze({
path: "/examples/reference-resources/status",
paramsSchema: null,
searchSchema: null,
access: "integration-defined",
access: "session-required",
loadingSurface: "reference-resource-status",
errorSurface: "feature-boundary",
chunkId: "route-reference-resource-status",
@@ -16,22 +16,38 @@ import type {
import type { ReferenceResourceView } from "../contracts/reference-mapper.ts";
import {
bindQuery,
defineServerStateProfile,
type BoundMutation,
type QueryResultMeasure,
} from "../../../contracts/server-state.ts";
import { useServerStateScope } from "../../../presentation/adapters/query/server-state-scope-provider.tsx";
const REFERENCE_READ_PROFILE = defineServerStateProfile({
profileId: "reference-resource-read-v1",
staleTimeMs: 30_000,
gcTimeMs: 300_000,
refetchOnMount: true,
refetchOnFocus: true,
refetchOnReconnect: true,
retryOwner: "TRANSPORT",
maxResultItems: 100,
maxEstimatedResultBytes: 262_144,
});
const UTF8 = new TextEncoder();
/**
* §10.4. Feature-owned measurement over the mapped application value. There is
* no generic fallback: bounded string bytes plus fixed primitive width plus a
* small per-item overhead, never `JSON.stringify` or a recursive walker.
*/
function measureResourceView(view: ReferenceResourceView): QueryResultMeasure {
return {
itemCount: 1,
estimatedBytes:
UTF8.encode(view.resourceId).byteLength +
UTF8.encode(view.title).byteLength +
UTF8.encode(view.createdAt ?? "").byteLength +
32,
};
}
function measureResourceList(
views: readonly ReferenceResourceView[],
): QueryResultMeasure {
let estimatedBytes = 16;
for (const view of views) {
estimatedBytes += measureResourceView(view).estimatedBytes;
}
return { itemCount: views.length, estimatedBytes };
}
export function useReferenceFeatureInput(): ReferenceFeatureInput {
return useApplication().features.get(REFERENCE_FEATURE_ID);
@@ -49,7 +65,8 @@ export function useReferenceDetail(resourceId: string) {
namespace: "reference-resource",
namespaceVersion: 1,
operationId: "GET_REFERENCE_RESOURCE",
profile: REFERENCE_READ_PROFILE,
profileId: "DETAIL_STANDARD",
measureResult: measureResourceView,
execute: (selectedResourceId: string, { signal }) =>
input.getResource(selectedResourceId, { signal }),
},
@@ -71,7 +88,7 @@ export function useReferenceCreate() {
definitionVersion: 1,
operationId: "CREATE_REFERENCE_RESOURCE",
owner: REFERENCE_FEATURE_ID,
duplicatePolicy: "JOIN_IDENTICAL",
duplicatePolicy: "REJECT_WHILE_ACTIVE",
scope,
execute: input.createResource,
invalidate: [REFERENCE_RESOURCE_INVALIDATION_TOPIC],
@@ -93,7 +110,8 @@ export function useReferenceFeature() {
namespace: "reference-resource",
namespaceVersion: 1,
operationId: "LIST_REFERENCE_RESOURCES",
profile: REFERENCE_READ_PROFILE,
profileId: "LIST_STANDARD",
measureResult: measureResourceList,
execute: (selectedFilters: ReferenceListFilters, { signal }) =>
input.listResources(selectedFilters, { signal }),
},
@@ -106,7 +124,7 @@ export function useReferenceFeature() {
definitionVersion: 1,
operationId: "CREATE_REFERENCE_RESOURCE",
owner: REFERENCE_FEATURE_ID,
duplicatePolicy: "JOIN_IDENTICAL",
duplicatePolicy: "REJECT_WHILE_ACTIVE",
scope,
execute: input.createResource,
invalidate: [REFERENCE_RESOURCE_INVALIDATION_TOPIC],