refactor: 프론트엔드 리펙토링

This commit is contained in:
donghyeon-ka
2026-09-18 22:05:42 +09:00
parent 5cc41467ae
commit ec7f20e2ee
100 changed files with 6005 additions and 2867 deletions
+13 -487
View File
@@ -32,319 +32,19 @@ import type {
} from "../../src/adapters/browser-transfer/resumable-upload/upload-cancellation-channel.ts";
import type { UploadMutationLock } from "../../src/adapters/browser-transfer/resumable-upload/upload-mutation-lock.ts";
type TestCapability = Readonly<{ id: string }>;
const activeSignal = new AbortController().signal;
const noContentionLock: UploadMutationLock = Object.freeze({
async run<Value>(
_uploadKey: string,
_signal: AbortSignal,
task: () => Promise<Value>,
): Promise<Value> {
return await task();
},
});
function createSerialMutationLock(): UploadMutationLock {
let tail = Promise.resolve();
return Object.freeze({
run<Value>(
_uploadKey: string,
signal: AbortSignal,
task: () => Promise<Value>,
): Promise<Value> {
const result = tail.then(async () => {
if (signal.aborted) {
throw new DOMException(
"The operation was aborted.",
"AbortError",
);
}
return await task();
});
tail = result.then(
() => undefined,
() => undefined,
);
return result;
},
});
}
function createMemoryCancellationPair(): readonly [
UploadCancellationChannel,
UploadCancellationChannel,
] {
const listeners = [
new Set<UploadCancellationListener>(),
new Set<UploadCancellationListener>(),
] as const;
const channels = listeners.map((ownListeners, ownIndex) => {
let closed = false;
return Object.freeze({
publish(uploadKey: string) {
if (closed) return false;
for (const [index, peerListeners] of listeners.entries()) {
if (index === ownIndex) continue;
for (const listener of [...peerListeners]) {
listener(uploadKey);
}
}
return true;
},
subscribe(listener: UploadCancellationListener) {
if (closed) throw new TypeError("closed");
ownListeners.add(listener);
return () => ownListeners.delete(listener);
},
close() {
closed = true;
ownListeners.clear();
},
});
});
return channels as unknown as readonly [
UploadCancellationChannel,
UploadCancellationChannel,
];
}
class MemoryCheckpointStore implements ResumableUploadCheckpointStore {
readonly rows = new Map<string, ResumableUploadCheckpoint>();
closed = false;
async read(
uploadKey: string,
): Promise<BrowserDataResult<ResumableUploadCheckpoint | null>> {
return browserDataSuccess(
structuredClone(this.rows.get(uploadKey) ?? null),
);
}
async compareAndSwap(
input: Parameters<
ResumableUploadCheckpointStore["compareAndSwap"]
>[0],
): Promise<BrowserDataResult<ResumableUploadCheckpoint>> {
const current = this.rows.get(input.checkpoint.uploadKey);
if (
(input.expectedRevision === null && current) ||
(input.expectedRevision !== null &&
current?.revision !== input.expectedRevision)
) {
return browserDataFailure("CONFLICT", "UPLOAD_RECONCILE", {
recovery: "RECONCILE",
});
}
const snapshot = structuredClone(input.checkpoint);
this.rows.set(snapshot.uploadKey, snapshot);
return browserDataSuccess(snapshot);
}
async remove(
input: Parameters<ResumableUploadCheckpointStore["remove"]>[0],
): Promise<BrowserDataResult<void>> {
const current = this.rows.get(input.uploadKey);
if (current?.revision !== input.expectedRevision) {
return browserDataFailure("CONFLICT", "UPLOAD_RECONCILE", {
recovery: "RECONCILE",
});
}
this.rows.delete(input.uploadKey);
return browserDataSuccess(undefined);
}
close(): void {
this.closed = true;
}
}
function rangeSource(bytes: Uint8Array): ResumableUploadSource {
return Object.freeze({
kind: "RANGE_READER" as const,
reader: Object.freeze({
byteLength: bytes.byteLength,
async readRange(input: Readonly<{
offset: number;
length: number;
signal: AbortSignal;
}>) {
if (input.signal.aborted) {
return browserDataFailure("ABORTED", "FILE_READ");
}
return browserDataSuccess(
bytes.slice(input.offset, input.offset + input.length),
);
},
}),
});
}
function byteStreamSource(bytes: Uint8Array): ResumableUploadSource {
return Object.freeze({
kind: "FILE_BYTE_SOURCE" as const,
bytes: Object.freeze({
byteLength: bytes.byteLength,
async *stream(signal: AbortSignal) {
if (signal.aborted) {
yield browserDataFailure("ABORTED", "FILE_READ");
return;
}
yield browserDataSuccess(bytes.slice(0, 3));
yield browserDataSuccess(bytes.slice(3));
},
}),
});
}
function runtimePolicy(
overrides: Partial<
Parameters<typeof resolveResumableUploadRuntimePolicy>[0]
> = {},
) {
return {
partSizeBytes: 4,
maxFileBytes: 100,
maxPartCount: 25,
maxConcurrency: 3,
maxInFlightBytes: 48,
partBufferCopyFactor: 4,
maxSourceChunkBytes: 8,
maxRetries: 2,
retryBaseDelayMs: 1,
retryMaxDelayMs: 10,
maxRetryAfterMs: 100,
capabilityRefreshSkewMs: 5,
maxSessionLifetimeMs: 10_000,
providerAttemptTimeoutMs: 100,
...overrides,
};
}
type ControlHarness = Readonly<{
control: ResumableUploadControlPlane<TestCapability>;
accepted: Map<number, UploadPartReceipt>;
issued: ReturnType<typeof vi.fn>;
completedParts: UploadPartReceipt[][];
getSession(): UploadSession | null;
}>;
function createControlHarness(options: Readonly<{
now?: number;
serverMaxConcurrency?: number;
sessionId?: (createIndex: number) => string;
statusParts?: (
session: UploadSession,
accepted: Map<number, UploadPartReceipt>,
) => readonly UploadPartReceipt[];
issueCapability?: (
input: Parameters<
ResumableUploadControlPlane<TestCapability>["issuePartCapability"]
>[0],
callIndex: number,
) => UploadProviderResult<Readonly<{
capability: TestCapability;
uploadBindingSha256: string;
expiresAtEpochMs: number;
}>>;
}> = {}): ControlHarness {
const now = options.now ?? 1_000;
const accepted = new Map<number, UploadPartReceipt>();
const completedParts: UploadPartReceipt[][] = [];
let session: UploadSession | null = null;
let createCount = 0;
let issueCount = 0;
const issued = vi.fn();
const control: ResumableUploadControlPlane<TestCapability> = {
async createSession(input) {
createCount += 1;
session = Object.freeze({
protocol: RESUMABLE_UPLOAD_PROTOCOL,
sessionId:
options.sessionId?.(createCount) ?? "session_01",
requestBindingSha256: input.requestBindingSha256,
fingerprint: input.fingerprint,
partSizeBytes: input.requestedPartSizeBytes,
partCount: input.fingerprint.partCount,
maxConcurrency: options.serverMaxConcurrency ?? 2,
expiresAtEpochMs: now + 5_000,
});
return browserDataSuccess(session);
},
async getStatus() {
if (!session) {
return browserDataFailure("NOT_FOUND", "UPLOAD_RECONCILE");
}
const parts =
options.statusParts?.(session, accepted) ??
[...accepted.values()].sort(
(left, right) => left.partNumber - right.partNumber,
);
return browserDataSuccess({
state: "ACTIVE",
session,
acceptedParts: parts,
});
},
async issuePartCapability(input) {
issueCount += 1;
issued(input);
return (
options.issueCapability?.(input, issueCount) ??
browserDataSuccess({
capability: Object.freeze({ id: `cap-${issueCount}` }),
uploadBindingSha256: input.uploadBindingSha256,
expiresAtEpochMs: now + 4_000,
})
);
},
async complete(input) {
completedParts.push([...input.orderedParts]);
return browserDataSuccess({
state: "QUARANTINED",
protocol: RESUMABLE_UPLOAD_PROTOCOL,
sessionId: input.sessionId,
requestBindingSha256: input.requestBindingSha256,
fingerprint: input.fingerprint,
resourceId: "resource_01",
});
},
async abort() {
return browserDataSuccess({ state: "ABORTED" });
},
};
return {
control,
accepted,
issued,
completedParts,
getSession: () => session,
};
}
function executorFor(
harness: ControlHarness,
options: Readonly<{
delay?: () => Promise<void>;
onActive?: (active: number) => void;
}> = {},
): UploadPartExecutor<TestCapability> {
let active = 0;
return {
async uploadPart(input) {
active += 1;
options.onActive?.(active);
await options.delay?.();
active -= 1;
const receipt = Object.freeze({
...input.part,
receiptToken: `etag-part-${input.part.partNumber}`,
});
harness.accepted.set(input.part.partNumber, receipt);
return browserDataSuccess(receipt);
},
};
}
import {
activeSignal,
byteStreamSource,
createControlHarness,
createMemoryCancellationPair,
createSerialMutationLock,
executorFor,
MemoryCheckpointStore,
noContentionLock,
rangeSource,
runtimePolicy,
type TestCapability,
} from "./resumable-upload-runtime-fixture.ts";
describe("production resumable upload runtime", () => {
it("disposes through one drain that proves quiescence", async () => {
@@ -1280,177 +980,3 @@ describe("production resumable upload runtime", () => {
}
});
});
/**
* TR-RR-06. A non-cooperative mutation lock or provider must not make teardown
* unbounded: `dispose()` bounds its drain and reports honestly when the runtime
* is still CLOSING, and an abort is admitted physical work it cannot step over.
*/
describe("TR-RR-06 bounded resumable teardown", () => {
it("reports an unproved drain instead of waiting forever", async () => {
const checkpoints = new MemoryCheckpointStore();
const harness = createControlHarness();
const runtime = createResumableUploadRuntime({
controlPlane: harness.control,
partExecutor: executorFor(harness, { delay: async () => {} }),
checkpoints,
// A lock that never grants: dispose must still be bounded.
mutationLock: Object.freeze({
async run<Value>(): Promise<Value> {
return await new Promise<never>(() => {});
},
}),
crypto,
policy: runtimePolicy({ cleanupDeadlineMs: 20 }),
now: () => 1_000,
random: () => 0,
sleep: async () => {},
});
void runtime.upload({
uploadKey: "upload_key_hung",
purpose: "attachment",
mediaType: "application/octet-stream",
source: byteStreamSource(new Uint8Array([1, 2, 3, 4, 5])),
signal: activeSignal,
});
await Promise.resolve();
await Promise.resolve();
const disposed = await runtime.dispose();
expect(disposed.ok).toBe(false);
// Still CLOSING: physical work the caller must not treat as finished.
expect(runtime.lifecycle()).toBe("CLOSING");
expect(checkpoints.closed).toBe(false);
});
it("closes once every admitted operation settles", async () => {
const checkpoints = new MemoryCheckpointStore();
const harness = createControlHarness();
const runtime = createResumableUploadRuntime({
controlPlane: harness.control,
partExecutor: executorFor(harness, { delay: async () => {} }),
checkpoints,
mutationLock: noContentionLock,
crypto,
policy: runtimePolicy({ cleanupDeadlineMs: 200 }),
now: () => 1_000,
random: () => 0,
sleep: async () => {},
});
const disposed = await runtime.dispose();
expect(disposed).toMatchObject({ ok: true });
expect(runtime.lifecycle()).toBe("CLOSED");
expect(checkpoints.closed).toBe(true);
});
});
/**
* TR-04. `dispose()` proved quiescence from the wrapper registry only. A
* provider that ignored its attempt deadline let the wrapper settle first and
* leave the set empty, so teardown reported a drained runtime — and closed the
* checkpoint store — while the provider was still running.
*/
describe("TR-04 teardown waits for raw provider work, not only its wrapper", () => {
it("refuses to report a drained runtime while a provider is still running", async () => {
const harness = createControlHarness();
const checkpoints = new MemoryCheckpointStore();
const closeStore = vi.spyOn(checkpoints, "close");
let releaseProvider: (() => void) | undefined;
// Ignores the attempt signal entirely and outlives its own deadline.
harness.control.createSession = () =>
new Promise((resolve) => {
releaseProvider = () =>
resolve(
browserDataFailure("UNAVAILABLE", "UPLOAD_SESSION", {
retryable: true,
recovery: "RESUME",
}),
);
});
const runtime = createResumableUploadRuntime({
controlPlane: harness.control,
partExecutor: executorFor(harness),
checkpoints,
mutationLock: noContentionLock,
crypto,
policy: runtimePolicy({
providerAttemptTimeoutMs: 5,
cleanupDeadlineMs: 25,
maxRetries: 0,
}),
now: () => 1_000,
random: () => 0,
sleep: async () => {},
});
const uploading = runtime.upload({
uploadKey: "upload_key_raw",
purpose: "attachment",
mediaType: "application/octet-stream",
source: byteStreamSource(new Uint8Array([1, 2, 3, 4, 5])),
signal: activeSignal,
});
await vi.waitFor(() => expect(releaseProvider).toBeDefined());
// The wrapper has already given up on the attempt.
await uploading;
const disposed = await runtime.dispose();
expect(disposed.ok).toBe(false);
if (!disposed.ok) {
expect(disposed.error.code).toBe("UNAVAILABLE");
expect(disposed.error.recovery).toBe("RESUME");
}
// The store stays open while something could still write a checkpoint.
expect(closeStore).not.toHaveBeenCalled();
releaseProvider?.();
});
it("reports a drained runtime once the raw provider settles", async () => {
const harness = createControlHarness();
const checkpoints = new MemoryCheckpointStore();
let releaseProvider: (() => void) | undefined;
harness.control.createSession = () =>
new Promise((resolve) => {
releaseProvider = () =>
resolve(
browserDataFailure("UNAVAILABLE", "UPLOAD_SESSION", {
retryable: true,
recovery: "RESUME",
}),
);
});
const runtime = createResumableUploadRuntime({
controlPlane: harness.control,
partExecutor: executorFor(harness),
checkpoints,
mutationLock: noContentionLock,
crypto,
policy: runtimePolicy({
providerAttemptTimeoutMs: 5,
cleanupDeadlineMs: 1_000,
maxRetries: 0,
}),
now: () => 1_000,
random: () => 0,
sleep: async () => {},
});
const uploading = runtime.upload({
uploadKey: "upload_key_raw_2",
purpose: "attachment",
mediaType: "application/octet-stream",
source: byteStreamSource(new Uint8Array([1, 2, 3, 4, 5])),
signal: activeSignal,
});
await vi.waitFor(() => expect(releaseProvider).toBeDefined());
await uploading;
const disposing = runtime.dispose();
releaseProvider?.();
await expect(disposing).resolves.toMatchObject({ ok: true });
expect(runtime.lifecycle()).toBe("CLOSED");
});
});