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
+268
View File
@@ -0,0 +1,268 @@
import {
createDemoSessionAdapter,
createExternalAuthSessionAdapter,
createUnavailableSessionAdapter,
type ExternalSessionOwner,
} from "../adapters/auth/external-session-adapter.ts";
import { createDiagnosticsAdapter } from "../adapters/diagnostics/bounded-diagnostics.ts";
import { createHttpClient } from "../adapters/http/client.ts";
import { createBrowserCrossContextInvalidationFromHost } from "../adapters/cross-context-invalidation/index.ts";
import { createTanStackCacheCoordinator } 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 { 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 {
fetchReleaseManifest,
type ReleaseManifest,
} from "./load-release-manifest.ts";
import type { RuntimeConfigLoadResult } from "./load-runtime-config.ts";
type HttpClientDependencies = Parameters<typeof createHttpClient>[0];
export type RuntimeHttpContract = Pick<
HttpClientDependencies,
"getOperation" | "validatePayload" | "validateRequest" | "mapPayload"
>;
type RuntimeHttpContext = Readonly<{
runtime: RuntimeConfigLoadResult;
authSession: AuthSessionPort;
fetcher?: typeof fetch;
clock?: ClockPort;
scheduler?: HttpClientDependencies["scheduler"];
diagnostics?: HttpClientDependencies["diagnostics"];
telemetry?: HttpClientDependencies["telemetry"];
}>;
export type RuntimeAdaptersContext = Readonly<{
runtime: RuntimeConfigLoadResult;
release: Readonly<ReleaseManifest>;
host?: Record<string, unknown>;
fetcher?: typeof fetch;
}>;
const EXTERNAL_OWNER_METHODS = Object.freeze([
"readState",
"subscribe",
"beginSignIn",
"signOut",
"attachCredential",
"recoverSession",
"notifyUnauthenticated",
] as const);
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === "object";
}
function externalOwnerFrom(
host: Record<string, unknown>,
): ExternalSessionOwner | null {
const candidate = host.__CA_FRONTEND_AUTH_OWNER__;
if (!isRecord(candidate)) return null;
return EXTERNAL_OWNER_METHODS.every(
(name) => typeof candidate[name] === "function",
)
? (candidate as ExternalSessionOwner)
: null;
}
function hostValue(
host: Record<string, unknown>,
property: string,
): unknown {
try {
return Reflect.get(host, property);
} catch {
return undefined;
}
}
function storageOrUndefined(value: unknown): Storage | undefined {
if (!value || typeof value !== "object") return undefined;
const candidate = value as Record<string, unknown>;
try {
return ["getItem", "setItem", "removeItem"].every(
(method) => typeof Reflect.get(candidate, method) === "function",
)
? (value as Storage)
: undefined;
} catch {
return undefined;
}
}
/**
* Runtime-aware transport factory. Feature gateway composition calls this
* factory when a registered API capability is installed.
*/
export function createRuntimeHttpClient(
context: RuntimeHttpContext,
contract: RuntimeHttpContract = {},
) {
return createHttpClient({
baseUrl: context.runtime.config.API_BASE_URL,
timeoutMs: context.runtime.config.REQUEST_TIMEOUT_MS,
maxRetryAttempts: context.runtime.config.MAX_RETRY_ATTEMPTS,
authSession: context.authSession,
providerProfile: createRestProviderProfile(
"PRIMARY_API",
context.runtime.config.API_BASE_URL,
["omit"],
),
fetcher: context.fetcher,
clock: context.clock,
scheduler: context.scheduler,
diagnostics: context.diagnostics,
telemetry: context.telemetry,
...contract,
});
}
export async function createRuntimeAdapters(
context: RuntimeAdaptersContext,
) {
const host =
context.host ?? (globalThis as unknown as Record<string, unknown>);
const config = context.runtime.config;
const externalOwner = externalOwnerFrom(host);
const authSession =
config.AUTH_MODE === "demo"
? createDemoSessionAdapter()
: externalOwner
? createExternalAuthSessionAdapter(externalOwner)
: createUnavailableSessionAdapter();
const diagnostics = createDiagnosticsAdapter();
const telemetry = createTelemetryAdapter({
enabled: config.TELEMETRY_ENABLED,
endpoint: config.TELEMETRY_ENDPOINT,
fetcher: context.fetcher,
onDrop(event) {
const attributes =
event.attributes && typeof event.attributes === "object"
? event.attributes
: {};
diagnostics.record({
level: "warn",
eventId: "telemetry.delivery.dropped",
context: attributes,
});
},
});
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,
},
});
},
});
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();
});
const storage = createBrowserStorageAdapter({
localStorage: storageOrUndefined(hostValue(host, "localStorage")),
sessionStorage: storageOrUndefined(
hostValue(host, "sessionStorage"),
),
diagnostics,
});
const releaseInfo = Object.freeze({
async getCurrent() {
return structuredClone(context.release);
},
async refresh() {
return fetchReleaseManifest(config.RELEASE_MANIFEST_URL, {
fetcher: context.fetcher,
buildId: context.release.buildId,
releaseId: context.release.releaseId,
});
},
});
const navigation = Object.freeze({
reload() {
const location = host.location;
if (!isRecord(location) || typeof location.reload !== "function") {
throw new Error("Browser reload is unavailable");
}
location.reload();
},
});
const featureInputs = createInstalledFeatureInputs({
createHttpClient: (contract) =>
createRuntimeHttpClient(
{
runtime: context.runtime,
authSession,
fetcher: context.fetcher,
diagnostics,
telemetry,
},
contract,
),
});
return Object.freeze({
outputPorts: Object.freeze({
session: authSession,
preferences: storage,
diagnostics,
telemetry,
releaseInfo,
navigation,
}),
infrastructure: Object.freeze({
queryClient,
queryInvalidation,
serverStateScope,
conditionalValidators,
crossContextInvalidationStatus: () =>
crossContextInvalidation?.getStatus() ?? "DEGRADED_LOCAL_ONLY",
dispose() {
unsubscribeConditionalScope();
conditionalValidators.clear();
serverStateScope.dispose();
queryInvalidation.dispose();
},
}),
featureInputs,
});
}