Files
core2026/app/game-frontend/src/utils/dashboardReadModel.ts
T
Hide_D a43f3e4e0a perf: 프런트엔드 import 경계를 세분화
Gateway 화면을 route 단위로 지연 로딩하고 common runtime import를 실제 소유 leaf export로 분리한다.\n\npackage boundary와 route loading 회귀 테스트로 root barrel 재유입을 차단한다.
2026-08-21 01:45:20 +00:00

98 lines
3.4 KiB
TypeScript

import {
createEmptyRealtimeReadModelInvalidation,
mergeRealtimeReadModelInvalidations,
resolveRealtimeReadModelInvalidation,
type RealtimeReadModelChanges,
type RealtimeReadModelInvalidation,
type RealtimeViewerIdentity,
} from '@sammo-ts/common/realtime/types';
export type DashboardReadModelIdentity = RealtimeViewerIdentity;
export type DashboardRefreshPlan = RealtimeReadModelInvalidation;
export type DashboardContextBundleInclude = {
context: boolean;
commandTable: boolean;
boardAccess: boolean;
};
export const resolveDashboardRefreshPlan = (
changes: RealtimeReadModelChanges,
identity: DashboardReadModelIdentity
): DashboardRefreshPlan => resolveRealtimeReadModelInvalidation(changes, identity);
export const resolveDashboardContextBundleInclude = (plan: DashboardRefreshPlan): DashboardContextBundleInclude => ({
context: plan.context,
commandTable: plan.commands,
boardAccess: plan.boardAccess,
});
type TimerHandle = ReturnType<typeof setTimeout>;
export interface MergedReadModelRefreshQueue {
request(invalidation: RealtimeReadModelInvalidation, refreshGrant: string): void;
cancelPending(): void;
}
export const createMergedReadModelRefreshQueue = (
refresh: (invalidation: RealtimeReadModelInvalidation, refreshGrant: string) => Promise<void>,
options: {
minIntervalMs?: number;
now?: () => number;
setTimer?: (callback: () => void, delayMs: number) => TimerHandle;
clearTimer?: (handle: TimerHandle) => void;
} = {}
): MergedReadModelRefreshQueue => {
const minIntervalMs = Math.max(0, options.minIntervalMs ?? 1_000);
const now = options.now ?? Date.now;
const setTimer = options.setTimer ?? ((callback, delayMs) => setTimeout(callback, delayMs));
const clearTimer = options.clearTimer ?? ((handle) => clearTimeout(handle));
let pending = createEmptyRealtimeReadModelInvalidation();
let pendingRefreshGrant = '';
let hasPending = false;
let running = false;
let timer: TimerHandle | null = null;
let lastStartedAt = Number.NEGATIVE_INFINITY;
const schedule = () => {
if (!hasPending || running || timer !== null) {
return;
}
const delayMs = Math.max(0, lastStartedAt + minIntervalMs - now());
timer = setTimer(() => {
timer = null;
if (!hasPending || running) {
return;
}
const next = pending;
const nextRefreshGrant = pendingRefreshGrant;
pending = createEmptyRealtimeReadModelInvalidation();
pendingRefreshGrant = '';
hasPending = false;
running = true;
lastStartedAt = now();
void refresh(next, nextRefreshGrant).finally(() => {
running = false;
schedule();
});
}, delayMs);
};
return {
request: (invalidation, refreshGrant) => {
pending = hasPending ? mergeRealtimeReadModelInvalidations(pending, invalidation) : invalidation;
pendingRefreshGrant = refreshGrant;
hasPending = true;
schedule();
},
cancelPending: () => {
hasPending = false;
pending = createEmptyRealtimeReadModelInvalidation();
pendingRefreshGrant = '';
if (timer !== null) {
clearTimer(timer);
timer = null;
}
},
};
};