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
+213 -61
View File
@@ -6,23 +6,32 @@ import {
} from "../adapters/auth/external-session-adapter.ts";
import { createDiagnosticsAdapter } from "../adapters/diagnostics/bounded-diagnostics.ts";
import { createHttpClient } from "../adapters/http/client.ts";
import { createContractHttpExecutor } from "../adapters/http/http-execution-v3.ts";
import { createBrowserCrossContextInvalidationFromHost } from "../adapters/cross-context-invalidation/index.ts";
import { createTanStackCacheCoordinator } from "../adapters/query-cache/tanstack-cache-coordinator.ts";
import {
createTanStackCacheCoordinator,
type InstalledQueryInvalidationDefinition,
} from "../adapters/query-cache/tanstack-cache-coordinator.ts";
import { createQueryClient } from "../adapters/query-cache/tanstack-query-cache.ts";
import { createServerStateScopeRuntime } from "../adapters/query-cache/server-state-scope-runtime.ts";
import { createConditionalValidatorStore } from "../adapters/query-cache/conditional-validator-store.ts";
import { createBrowserStorageAdapter } from "../adapters/storage/browser-storage-adapter.ts";
import { createTelemetryAdapter } from "../adapters/telemetry/best-effort-telemetry.ts";
import type { AuthSessionPort } from "../application/ports/auth-session-port.ts";
import type { ReleaseInfo } from "../application/ports/release-info-port.ts";
import { createRestProviderProfile } from "../contracts/rest-profiles.ts";
import type { ClockPort } from "../application/ports/clock-port.ts";
import { createInstalledFeatureInputs } from "../features/installed-feature-adapters.ts";
import { QUERY_REGISTRY } from "../features/installed-feature-contracts.ts";
import { INSTALLED_RUNTIME_CAPABILITIES } from "../features/installed-runtime-capabilities.ts";
import { describeRuntimeCapabilities } from "../contracts/runtime-capabilities.ts";
import {
fetchReleaseManifest,
type ReleaseManifest,
} from "./load-release-manifest.ts";
import type { RuntimeConfigLoadResult } from "./load-runtime-config.ts";
import { createServerStateGenerationStore } from "./server-state-generation-store.ts";
import { COMPOSED_CONTRACT_CONTRIBUTIONS } from "../features/installed-contract-contributions.ts";
type HttpClientDependencies = Parameters<typeof createHttpClient>[0];
export type RuntimeHttpContract = Pick<
@@ -47,6 +56,26 @@ export type RuntimeAdaptersContext = Readonly<{
fetcher?: typeof fetch;
}>;
/**
* §5.2. The composition root is where a manifest becomes release info. A V2
* manifest states contract identity as a verified contract set and a V1
* manifest as the legacy scalar; the application layer sees one shape and never
* branches on the schema version to find the identity.
*/
function toReleaseInfo(manifest: Readonly<ReleaseManifest>): ReleaseInfo {
const { contractSet, legacyApiContractVersion, ...rest } =
structuredClone(manifest);
return Object.freeze({
...rest,
...(legacyApiContractVersion === undefined
? {}
: { apiContractVersion: legacyApiContractVersion }),
...(contractSet === null
? {}
: { contractSetDigest: contractSet.setDigest }),
});
}
const EXTERNAL_OWNER_METHODS = Object.freeze([
"readState",
"subscribe",
@@ -155,48 +184,69 @@ export async function createRuntimeAdapters(
});
},
});
const queryClient = createQueryClient({ diagnostics });
const crossContextInvalidation =
createBrowserCrossContextInvalidationFromHost({
...(context.host === undefined ? {} : { host: context.host }),
cacheEpoch: `release.${context.release.releaseId}`,
topics: Object.values(QUERY_REGISTRY).map((definition) =>
Object.freeze({
topic: definition.invalidationTopic,
topicVersion: definition.version,
}),
),
observe(observation) {
if (
observation.outcome !== "FAILED" &&
observation.outcome !== "DEGRADED"
) {
return;
}
diagnostics.record({
level: "warn",
eventId: "cache.operation.failed",
context: {
operation: observation.operation,
outcome: observation.outcome,
reason: observation.reason,
},
});
},
// The composition root states the registry shape it consumes rather than
// inferring it from whichever features happen to be installed, so a build
// with zero installed features still type-checks.
const queryRegistry: Readonly<
Record<string, InstalledQueryInvalidationDefinition>
> = QUERY_REGISTRY;
const conditionalValidators = createConditionalValidatorStore();
const serverStateGeneration = createServerStateGenerationStore(() => {
const queryClient = createQueryClient({ diagnostics });
const crossContextInvalidation =
createBrowserCrossContextInvalidationFromHost({
...(context.host === undefined ? {} : { host: context.host }),
cacheEpoch: `release.${context.release.releaseId}`,
topics: Object.values(queryRegistry).map((definition) =>
Object.freeze({
topic: definition.invalidationTopic,
topicVersion: definition.version,
}),
),
observe(observation) {
if (
observation.outcome !== "FAILED" &&
observation.outcome !== "DEGRADED"
) {
return;
}
diagnostics.record({
level: "warn",
eventId: "cache.operation.failed",
context: {
operation: observation.operation,
outcome: observation.outcome,
reason: observation.reason,
},
});
},
});
const queryInvalidation = createTanStackCacheCoordinator({
queryClient,
queryRegistry,
crossContext: crossContextInvalidation,
diagnostics,
});
return Object.freeze({
queryClient,
queryInvalidation,
crossContextStatus: () =>
crossContextInvalidation?.getStatus() ?? "DEGRADED_LOCAL_ONLY",
});
const queryInvalidation = createTanStackCacheCoordinator({
queryClient,
queryRegistry: QUERY_REGISTRY,
crossContext: crossContextInvalidation,
diagnostics,
});
const serverStateScope = createServerStateScopeRuntime({
session: authSession,
queryInvalidation,
});
const conditionalValidators = createConditionalValidatorStore();
const unsubscribeConditionalScope = serverStateScope.subscribe(() => {
conditionalValidators.clear();
queryInvalidation: {
resetLocal: () => serverStateGeneration.resetCurrent(),
},
participants: [
{
order: 4,
label: "conditional-validators",
close: () => conditionalValidators.clear(),
},
],
activateNextGeneration: () => serverStateGeneration.activateNext(),
});
const storage = createBrowserStorageAdapter({
localStorage: storageOrUndefined(hostValue(host, "localStorage")),
@@ -207,14 +257,24 @@ export async function createRuntimeAdapters(
});
const releaseInfo = Object.freeze({
async getCurrent() {
return structuredClone(context.release);
return toReleaseInfo(context.release);
},
async refresh() {
return fetchReleaseManifest(config.RELEASE_MANIFEST_URL, {
fetcher: context.fetcher,
buildId: context.release.buildId,
releaseId: context.release.releaseId,
});
return toReleaseInfo(
await fetchReleaseManifest(config.RELEASE_MANIFEST_URL, {
fetcher: context.fetcher,
buildId: context.release.buildId,
releaseId: context.release.releaseId,
}),
);
},
});
const runtimeCapabilities = Object.freeze({
getSnapshot() {
return describeRuntimeCapabilities(
INSTALLED_RUNTIME_CAPABILITIES,
config.CAPABILITY_OVERRIDES,
);
},
});
const navigation = Object.freeze({
@@ -226,18 +286,105 @@ export async function createRuntimeAdapters(
location.reload();
},
});
const contractHttp = createContractHttpExecutor({
baseUrl: config.API_BASE_URL,
maxRetryAttempts: config.MAX_RETRY_ATTEMPTS,
fetcher: context.fetcher,
async attachCredentials(operation) {
if (serverStateScope.getPhase() !== "READY") {
return Object.freeze({ kind: "SCOPE_FENCED" as const });
}
const state = authSession.getState();
if (state === "integration-failed") {
return Object.freeze({ kind: "UNAVAILABLE" as const });
}
if (state !== "authenticated") {
return Object.freeze({ kind: "UNAUTHENTICATED" as const });
}
try {
const patch = await authSession.credentialPatch({
origin: new URL(config.API_BASE_URL).origin,
method: operation.method,
operationId: operation.operationId,
});
if (serverStateScope.getPhase() !== "READY") {
return Object.freeze({ kind: "SCOPE_FENCED" as const });
}
return Object.freeze({
kind: "READY" as const,
headers: patch.headers,
credentials: "omit" as const,
});
} catch {
return Object.freeze({ kind: "UNAVAILABLE" as const });
}
},
observe(observation) {
try {
diagnostics.record({
level:
observation.outcome === "SUCCESS" ? "info" : "warn",
eventId: "http.request.completed",
context: {
operation_id: observation.diagnosticsOperation,
outcome: observation.outcome,
attempts: observation.attempts,
certainty: observation.certainty,
},
});
} catch {
// Diagnostics cannot change a contract execution outcome.
}
},
});
let contractExecutionSequence = 0;
const contractOperations = Object.freeze({
async execute(
operationId: string,
input: unknown,
executionContext: Readonly<{ signal?: AbortSignal }> = {},
) {
const operation =
COMPOSED_CONTRACT_CONTRIBUTIONS.httpByOperationId.get(operationId);
if (!operation) {
return Object.freeze({
kind: "CONTRACT_VIOLATION" as const,
effect: "NOT_STARTED" as const,
violation: Object.freeze({
kind: "FINAL_REQUEST_INVARIANT_FAILED" as const,
operation: "REQUEST" as const,
}),
});
}
contractExecutionSequence += 1;
const intentId = `http-intent-${contractExecutionSequence}`;
const isCommand = operation.contract.commandEffect !== null;
const requiresKey = operation.contract.retrySemantics === "KEYED";
const outcome = await contractHttp.execute(operation, input, {
scope: serverStateScope.getSnapshot(),
...(executionContext.signal === undefined
? {}
: { signal: executionContext.signal }),
...(isCommand
? {
intent: Object.freeze({
intentId,
startedBy: "USER" as const,
...(requiresKey
? { idempotencyKey: `http-key-${contractExecutionSequence}` }
: {}),
}),
}
: {}),
});
if (outcome.kind === "UNAUTHENTICATED") {
authSession.onUnauthenticated();
}
return outcome;
},
});
const featureInputs = createInstalledFeatureInputs({
createHttpClient: (contract) =>
createRuntimeHttpClient(
{
runtime: context.runtime,
authSession,
fetcher: context.fetcher,
diagnostics,
telemetry,
},
contract,
),
contractOperations,
});
return Object.freeze({
@@ -247,20 +394,25 @@ export async function createRuntimeAdapters(
diagnostics,
telemetry,
releaseInfo,
runtimeCapabilities,
navigation,
}),
infrastructure: Object.freeze({
queryClient,
queryInvalidation,
get queryClient() {
return serverStateGeneration.getSnapshot().queryClient;
},
get queryInvalidation() {
return serverStateGeneration.getSnapshot().queryInvalidation;
},
serverStateGeneration,
serverStateScope,
conditionalValidators,
crossContextInvalidationStatus: () =>
crossContextInvalidation?.getStatus() ?? "DEGRADED_LOCAL_ONLY",
serverStateGeneration.getSnapshot().crossContextStatus(),
dispose() {
unsubscribeConditionalScope();
conditionalValidators.clear();
serverStateScope.dispose();
queryInvalidation.dispose();
serverStateGeneration.dispose();
},
}),
featureInputs,