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
+123 -64
View File
@@ -1,79 +1,138 @@
import { z } from "zod";
import {
runtimeConfigV1ArtifactSchema,
runtimeConfigV2ArtifactSchema,
type CapabilityOverrideArtifact,
type RuntimeConfigV1Artifact,
type RuntimeConfigV2Artifact,
} from "../contracts/release-artifacts.ts";
const version = z.string().regex(/^\d+(?:\.\d+){0,2}$/);
export { isValidReleaseManifestUrl } from "../contracts/release-artifacts.ts";
export const runtimeConfigSchema = z
.object({
APP_ENV: z.enum(["local", "development", "staging", "production"]),
API_BASE_URL: z.url(),
REQUEST_TIMEOUT_MS: z.int().min(100).max(60_000).default(10_000),
MAX_RETRY_ATTEMPTS: z.int().min(0).max(2).default(2),
TELEMETRY_ENABLED: z.boolean(),
TELEMETRY_ENDPOINT: z.url().optional(),
AUTH_MODE: z.enum(["external", "demo"]),
CONFIG_SCHEMA_VERSION: version,
API_CONTRACT_VERSION: version,
RELEASE_MANIFEST_URL: z.string().min(1).default("/release-manifest.json"),
RELEASE_ID: z.string().min(1).optional(),
BUILD_ID: z.string().min(1).optional(),
})
.strict()
.superRefine((config, context) => {
if (config.TELEMETRY_ENABLED && !config.TELEMETRY_ENDPOINT) {
context.addIssue({
code: "custom",
path: ["TELEMETRY_ENDPOINT"],
message: "required when telemetry is enabled",
});
}
export type CapabilityOverrides = CapabilityOverrideArtifact;
const local = config.APP_ENV === "local" || config.APP_ENV === "development";
if (!local && config.AUTH_MODE === "demo") {
context.addIssue({
code: "custom",
path: ["AUTH_MODE"],
message: "demo authentication is limited to local environments",
});
}
const endpointEntries = [
["API_BASE_URL", config.API_BASE_URL],
["TELEMETRY_ENDPOINT", config.TELEMETRY_ENDPOINT],
] as const;
/**
* §6.1. Runtime Config V2 is deployment and browser operational setting only.
* `API_CONTRACT_VERSION` is gone: a scalar cannot describe a multi-package
* contract set, and Release Manifest V2 `contractSet` owns that meaning.
*/
export const runtimeConfigV2Schema = runtimeConfigV2ArtifactSchema;
for (const [key, value] of endpointEntries) {
if (value && !local && new URL(value).protocol !== "https:") {
context.addIssue({
code: "custom",
path: [key],
message: "HTTPS is required outside local environments",
});
}
}
});
/**
* §5.8 / §24.6 RC-2. The V1 reader is retained for one compatibility window so
* a release never swaps source shape, manifest shape and runtime behaviour at
* the same time. Only a V1 document may carry the scalar contract version.
*/
export const runtimeConfigV1Schema = runtimeConfigV1ArtifactSchema;
export type RuntimeConfigV2 = RuntimeConfigV2Artifact;
export type RuntimeConfigV1 = RuntimeConfigV1Artifact;
/**
* The composition-facing shape. V1 documents are normalized onto it so the rest
* of the runtime never branches on config schema version.
*/
export type RuntimeConfig = Readonly<{
APP_ENV: RuntimeConfigV2["APP_ENV"];
API_BASE_URL: string;
REQUEST_TIMEOUT_MS: number;
MAX_RETRY_ATTEMPTS: number;
TELEMETRY_ENABLED: boolean;
TELEMETRY_ENDPOINT?: string;
AUTH_MODE: RuntimeConfigV2["AUTH_MODE"];
CONFIG_SCHEMA_VERSION: string;
RELEASE_MANIFEST_URL: string;
RELEASE_ID?: string;
BUILD_ID?: string;
CAPABILITY_OVERRIDES: CapabilityOverrides;
/** Present only while a V1 document is still accepted. */
LEGACY_API_CONTRACT_VERSION?: string;
}>;
export type RuntimeConfig = z.output<typeof runtimeConfigSchema>;
export type RuntimeConfigValidation =
| Readonly<{ success: true; data: RuntimeConfig }>
| Readonly<{ success: true; data: RuntimeConfig; schema: "V1" | "V2" }>
| Readonly<{
success: false;
issues: readonly Readonly<{ path: string; code: string }>[];
}>;
export function validateRuntimeConfig(value: unknown): RuntimeConfigValidation {
const result = runtimeConfigSchema.safeParse(value);
const DEFAULT_OVERRIDES: CapabilityOverrides = Object.freeze({
REALTIME: "DEFAULT" as const,
WEB_WORKER: "DEFAULT" as const,
SERVICE_WORKER: "DEFAULT" as const,
OFFLINE_COMMANDS: "DEFAULT" as const,
});
if (!result.success) {
return {
success: false,
issues: result.error.issues.map((issue) => ({
path: issue.path.join("."),
code: issue.code,
})),
};
function canonicalUrl(value: string): string {
return new URL(value).href;
}
export function validateRuntimeConfig(value: unknown): RuntimeConfigValidation {
const declared =
value && typeof value === "object"
? (value as Record<string, unknown>).CONFIG_SCHEMA_VERSION
: undefined;
// §5.8: no precedence between V1 and V2. The declared version selects exactly
// one parser, and a V2 document carrying the removed scalar is rejected.
if (declared !== "1" && declared !== "2.0") {
return Object.freeze({
success: false as const,
issues: Object.freeze([
Object.freeze({
path: "CONFIG_SCHEMA_VERSION",
code: "unsupported_value",
}),
]),
});
}
return {
success: true,
data: structuredClone(result.data),
};
const isV2 = declared === "2.0";
const result = isV2
? runtimeConfigV2Schema.safeParse(value)
: runtimeConfigV1Schema.safeParse(value);
if (!result.success) {
return Object.freeze({
success: false as const,
issues: Object.freeze(
result.error.issues.map((issue) =>
Object.freeze({ path: issue.path.join("."), code: issue.code }),
),
),
});
}
const parsed = result.data;
const normalized: RuntimeConfig = Object.freeze({
APP_ENV: parsed.APP_ENV,
API_BASE_URL: canonicalUrl(parsed.API_BASE_URL),
REQUEST_TIMEOUT_MS: parsed.REQUEST_TIMEOUT_MS,
MAX_RETRY_ATTEMPTS: parsed.MAX_RETRY_ATTEMPTS,
TELEMETRY_ENABLED: parsed.TELEMETRY_ENABLED,
...(parsed.TELEMETRY_ENDPOINT
? { TELEMETRY_ENDPOINT: canonicalUrl(parsed.TELEMETRY_ENDPOINT) }
: {}),
AUTH_MODE: parsed.AUTH_MODE,
CONFIG_SCHEMA_VERSION: parsed.CONFIG_SCHEMA_VERSION,
RELEASE_MANIFEST_URL: parsed.RELEASE_MANIFEST_URL,
...(parsed.RELEASE_ID ? { RELEASE_ID: parsed.RELEASE_ID } : {}),
...(parsed.BUILD_ID ? { BUILD_ID: parsed.BUILD_ID } : {}),
CAPABILITY_OVERRIDES: Object.freeze({
...(isV2
? (parsed as RuntimeConfigV2).CAPABILITY_OVERRIDES
: DEFAULT_OVERRIDES),
}),
...(isV2
? {}
: {
LEGACY_API_CONTRACT_VERSION: (parsed as RuntimeConfigV1)
.API_CONTRACT_VERSION,
}),
});
return Object.freeze({
success: true as const,
data: normalized,
schema: isV2 ? ("V2" as const) : ("V1" as const),
});
}