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
@@ -6,18 +6,47 @@ import {
} from "../../contracts/query-keys.ts";
import type {
CacheScopeSnapshot,
ClientScopeLifecycleEvent,
ClientScopePhase,
ServerStateScopeRuntime,
} from "../../contracts/server-state-scope.ts";
export function createServerStateScopeRuntime(dependencies: Readonly<{
/**
* Steps 4-11 of §10.6 that this runtime does not own directly. Each optional
* capability registers its own closer so the ordering lives in one place rather
* than being re-derived by every subsystem.
*/
export type ScopeResetParticipant = Readonly<{
/** Lower runs earlier; the §10.6 step number is used as the rank. */
order: number;
label: string;
close(): void | Promise<void>;
}>;
export type ServerStateScopeDependencies = Readonly<{
session: Pick<AuthSessionPort, "subscribe">;
queryInvalidation: QueryInvalidationCoordinator;
queryInvalidation: Pick<QueryInvalidationCoordinator, "resetLocal">;
tokenFactory?: () => string;
}>): ServerStateScopeRuntime {
participants?: readonly ScopeResetParticipant[];
activateNextGeneration?: () => void | Promise<void>;
}>;
export function createServerStateScopeRuntime(
dependencies: ServerStateScopeDependencies,
): ServerStateScopeRuntime {
const listeners = new Set<() => void>();
const lifecycleListeners = new Set<
(event: ClientScopeLifecycleEvent) => void
>();
const participants = [...(dependencies.participants ?? [])].sort(
(left, right) => left.order - right.order,
);
let generation = 1;
let identities = newIdentityRegistry(dependencies.tokenFactory);
let fingerprint = scopeFingerprint(dependencies.tokenFactory);
let generationLifetime = new AbortController();
let phase: ClientScopePhase = "READY";
let disposed = false;
let resetChain = Promise.resolve();
@@ -28,42 +57,129 @@ export function createServerStateScopeRuntime(dependencies: Readonly<{
generation: capturedGeneration,
fingerprint,
identities: capturedIdentities,
signal: generationLifetime.signal,
isCurrent: () =>
!disposed &&
phase === "READY" &&
generation === capturedGeneration &&
identities === capturedIdentities,
});
}
let currentSnapshot = createSnapshot();
function publishLifecycle(event: ClientScopeLifecycleEvent): void {
for (const listener of [...lifecycleListeners]) {
try {
listener(event);
} catch {
// One subscriber defect cannot stop the fence from propagating.
}
}
}
function publishSnapshot(): void {
for (const listener of [...listeners]) {
try {
listener();
} catch {
// Subscriber defects are isolated from the mandatory reset sequence.
}
}
}
const unsubscribe = dependencies.session.subscribe(() => {
if (disposed) return;
const previousIdentities = identities;
const previousGeneration = generation;
// §10.6 steps 1-3 are synchronous: increment the generation, invalidate the
// old snapshot, publish FENCED. Nothing between here and READY may render a
// value that belonged to the previous identity.
generationLifetime.abort();
const targetGeneration = ++generation;
phase = "FENCED";
currentSnapshot = createSnapshot();
publishLifecycle(
Object.freeze({ kind: "FENCED" as const, previousGeneration }),
);
publishSnapshot();
resetChain = resetChain
.then(() => dependencies.queryInvalidation.resetLocal())
.catch(() => {})
.finally(() => {
.then(async () => {
let failed = false;
// Steps 4-11: close admission, cancel and clear, release leases.
for (const participant of participants) {
try {
await participant.close();
} catch {
failed = true;
}
}
try {
await dependencies.queryInvalidation.resetLocal();
} catch {
failed = true;
}
previousIdentities.close();
if (disposed || generation !== targetGeneration) return;
if (!failed) {
try {
await dependencies.activateNextGeneration?.();
} catch {
failed = true;
}
}
if (disposed || generation !== targetGeneration) return;
if (failed) {
phase = "FAILED";
currentSnapshot = createSnapshot();
publishLifecycle(
Object.freeze({
kind: "FAILED" as const,
generation: targetGeneration,
}),
);
publishSnapshot();
return;
}
// Steps 12-15: new identity registry, READY, notify, reopen admission.
identities = newIdentityRegistry(dependencies.tokenFactory);
fingerprint = scopeFingerprint(dependencies.tokenFactory);
generationLifetime = new AbortController();
phase = "READY";
currentSnapshot = createSnapshot();
for (const listener of listeners) listener();
publishLifecycle(
Object.freeze({ kind: "READY" as const, snapshot: currentSnapshot }),
);
publishSnapshot();
});
});
return Object.freeze({
getSnapshot: () => currentSnapshot,
subscribe(listener) {
getPhase: () => phase,
subscribe(listener: () => void) {
listeners.add(listener);
return () => listeners.delete(listener);
},
subscribeLifecycle(listener: (event: ClientScopeLifecycleEvent) => void) {
lifecycleListeners.add(listener);
return () => lifecycleListeners.delete(listener);
},
dispose() {
if (disposed) return;
disposed = true;
phase = "DISPOSED";
generationLifetime.abort();
unsubscribe();
publishLifecycle(Object.freeze({ kind: "DISPOSED" as const }));
listeners.clear();
lifecycleListeners.clear();
identities.close();
},
});