feat: send dashboard read-model deltas
This commit is contained in:
@@ -5,6 +5,7 @@ import { expect, test, type Page, type Route } from '@playwright/test';
|
||||
const response = (data: unknown) => ({ result: { data } });
|
||||
const artifactRoot = process.env.MAIN_NAVIGATION_ARTIFACT_DIR;
|
||||
const autoRefreshArtifactRoot = process.env.AUTO_REFRESH_ARTIFACT_DIR;
|
||||
const productionBundle = process.env.PLAYWRIGHT_FRONTEND_MODE === 'production';
|
||||
const basePath = `/${(process.env.PLAYWRIGHT_GAME_BASE_PATH ?? 'che').replace(/^\/+|\/+$/g, '')}`;
|
||||
const gameProfile = process.env.PLAYWRIGHT_GAME_PROFILE ?? 'che:default';
|
||||
const operationNames = (route: Route) =>
|
||||
@@ -20,6 +21,95 @@ type NavigationFixture = {
|
||||
operations: string[];
|
||||
generalName?: string;
|
||||
refreshDelayMs?: number;
|
||||
largeCommandTable?: boolean;
|
||||
dashboardResponses?: Array<{
|
||||
bytes: number;
|
||||
contextKind: string | null;
|
||||
commandTableKind: string | null;
|
||||
boardAccessKind: string | null;
|
||||
}>;
|
||||
};
|
||||
|
||||
type DashboardBundleInput = {
|
||||
include?: { context?: boolean; commandTable?: boolean; boardAccess?: boolean };
|
||||
known?: { context?: string; commandTable?: string; boardAccess?: string };
|
||||
forceSnapshot?: boolean;
|
||||
};
|
||||
|
||||
const operationInput = (route: Route, index: number): DashboardBundleInput => {
|
||||
const input = new URL(route.request().url()).searchParams.get('input');
|
||||
if (!input) return {};
|
||||
const parsed = JSON.parse(input) as Record<string, unknown>;
|
||||
const entry = (parsed[String(index)] ?? parsed) as { json?: DashboardBundleInput };
|
||||
return entry.json ?? (entry as DashboardBundleInput);
|
||||
};
|
||||
|
||||
const commandTableFixture = (large: boolean) => ({
|
||||
general: large
|
||||
? [
|
||||
{
|
||||
category: '일반',
|
||||
values: Array.from({ length: 48 }, (_, index) => ({
|
||||
key: `command-${index}`,
|
||||
name: `명령 ${index}`,
|
||||
reqArg: index % 2 === 0,
|
||||
possible: true,
|
||||
status: 'available',
|
||||
inputFields: [
|
||||
{
|
||||
key: 'amount',
|
||||
label: '수량',
|
||||
kind: 'number',
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 10_000,
|
||||
},
|
||||
],
|
||||
})),
|
||||
},
|
||||
]
|
||||
: [],
|
||||
nation: [],
|
||||
inputOptions: {
|
||||
cities: Array.from({ length: 20 }, (_, index) => ({ value: index + 1, label: `도시 ${index + 1}` })),
|
||||
nations: [],
|
||||
generals: [],
|
||||
crewTypes: [],
|
||||
armTypes: [],
|
||||
nationTypes: [],
|
||||
colors: [],
|
||||
items: {},
|
||||
},
|
||||
});
|
||||
|
||||
const CONTEXT_INITIAL_REVISION = 'AAAAAAAAAAAAAAAAAAAAAA';
|
||||
const COMMAND_TABLE_REVISION = 'CCCCCCCCCCCCCCCCCCCCCC';
|
||||
const BOARD_ACCESS_REVISION = 'DDDDDDDDDDDDDDDDDDDDDD';
|
||||
|
||||
const contextRevision = (state: NavigationFixture) => {
|
||||
const name = state.generalName ?? '메뉴검증장수';
|
||||
if (name === '메뉴검증장수') return CONTEXT_INITIAL_REVISION;
|
||||
if (name === '부드럽게갱신된장수') return 'EEEEEEEEEEEEEEEEEEEEEE';
|
||||
if (name === '탭공유갱신장수') return 'FFFFFFFFFFFFFFFFFFFFFF';
|
||||
if (name === '리더만갱신장수') return 'GGGGGGGGGGGGGGGGGGGGGG';
|
||||
return 'HHHHHHHHHHHHHHHHHHHHHH';
|
||||
};
|
||||
|
||||
const deltaSlice = <T>(value: T, revision: string, known: string | undefined, forceSnapshot: boolean) => {
|
||||
if (forceSnapshot || !known) return { kind: 'snapshot' as const, revision, data: value };
|
||||
if (known === revision) return { kind: 'unchanged' as const, revision };
|
||||
return {
|
||||
kind: 'patch' as const,
|
||||
baseRevision: known,
|
||||
revision,
|
||||
operations: [
|
||||
{
|
||||
op: 'replace' as const,
|
||||
path: '/general/name',
|
||||
value: (value as ReturnType<typeof generalContext>).general.name,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const emptyMessages = (permission: number) => ({
|
||||
@@ -134,14 +224,51 @@ const installFixture = async (page: Page, state: NavigationFixture) => {
|
||||
await page.route(`**${basePath}/api/trpc/**`, async (route) => {
|
||||
const operations = operationNames(route);
|
||||
state.operations.push(...operations);
|
||||
if (operations.includes('general.me') && state.generalMeCalls > 0 && state.refreshDelayMs) {
|
||||
if (
|
||||
operations.some((operation) => ['general.me', 'dashboard.getContextBundleDelta'].includes(operation)) &&
|
||||
state.generalMeCalls > 0 &&
|
||||
state.refreshDelayMs
|
||||
) {
|
||||
await new Promise((resolve) => setTimeout(resolve, state.refreshDelayMs));
|
||||
}
|
||||
const results = operations.map((operation) => {
|
||||
const results = operations.map((operation, index) => {
|
||||
if (operation === 'auth.status') return response({ ok: true });
|
||||
if (operation === 'lobby.info') {
|
||||
return response({ myGeneral: { id: 7, name: '메뉴검증장수' }, year: 185, month: 1, turnTerm: 10 });
|
||||
}
|
||||
if (operation === 'dashboard.getContextBundleDelta') {
|
||||
state.generalMeCalls += 1;
|
||||
const input = operationInput(route, index);
|
||||
const include = input.include ?? {};
|
||||
const forceSnapshot = input.forceSnapshot === true;
|
||||
const revision = contextRevision(state);
|
||||
const context = include.context
|
||||
? deltaSlice(generalContext(state), revision, input.known?.context, forceSnapshot)
|
||||
: undefined;
|
||||
const commandTable = include.commandTable
|
||||
? forceSnapshot || !input.known?.commandTable
|
||||
? {
|
||||
kind: 'snapshot' as const,
|
||||
revision: COMMAND_TABLE_REVISION,
|
||||
data: commandTableFixture(state.largeCommandTable === true),
|
||||
}
|
||||
: { kind: 'unchanged' as const, revision: COMMAND_TABLE_REVISION }
|
||||
: undefined;
|
||||
const boardAccess = include.boardAccess
|
||||
? forceSnapshot || !input.known?.boardAccess
|
||||
? {
|
||||
kind: 'snapshot' as const,
|
||||
revision: BOARD_ACCESS_REVISION,
|
||||
data: {
|
||||
permission: state.permission,
|
||||
canMeeting: state.officerLevel >= 1,
|
||||
canSecret: state.permission >= 2,
|
||||
},
|
||||
}
|
||||
: { kind: 'unchanged' as const, revision: BOARD_ACCESS_REVISION }
|
||||
: undefined;
|
||||
return response({ context, commandTable, boardAccess });
|
||||
}
|
||||
if (operation === 'general.me') {
|
||||
state.generalMeCalls += 1;
|
||||
return response(generalContext(state));
|
||||
@@ -211,6 +338,22 @@ const installFixture = async (page: Page, state: NavigationFixture) => {
|
||||
if (operation === 'tournament.getState') return response({ stage: state.stage });
|
||||
return response({ ok: true });
|
||||
});
|
||||
operations.forEach((operation, index) => {
|
||||
if (operation !== 'dashboard.getContextBundleDelta') return;
|
||||
const item = results[index];
|
||||
if (!item) return;
|
||||
const data = item.result.data as {
|
||||
context?: { kind: string };
|
||||
commandTable?: { kind: string };
|
||||
boardAccess?: { kind: string };
|
||||
};
|
||||
(state.dashboardResponses ??= []).push({
|
||||
bytes: Buffer.byteLength(JSON.stringify(item)),
|
||||
contextKind: data.context?.kind ?? null,
|
||||
commandTableKind: data.commandTable?.kind ?? null,
|
||||
boardAccessKind: data.boardAccess?.kind ?? null,
|
||||
});
|
||||
});
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
@@ -638,6 +781,7 @@ test('realtime read-model events skip clock-only work, merge bursts, patch in pl
|
||||
generalMeCalls: 0,
|
||||
operations: [],
|
||||
refreshDelayMs: 300,
|
||||
largeCommandTable: true,
|
||||
};
|
||||
await installRealtimeHarness(page);
|
||||
await installFixture(page, state);
|
||||
@@ -739,11 +883,12 @@ test('realtime read-model events skip clock-only work, merge bursts, patch in pl
|
||||
expect(state.generalMeCalls).toBe(callsBeforeRefresh + 1);
|
||||
await expect(page.locator('.general-title')).toContainText('부드럽게갱신된장수');
|
||||
const changedOperations = state.operations.slice(operationsBeforeChangedBurst);
|
||||
expect(changedOperations).toEqual(
|
||||
expect.arrayContaining(['general.me', 'turns.getCommandTable', 'board.getAccess'])
|
||||
);
|
||||
expect(changedOperations).toEqual(['dashboard.getContextBundleDelta']);
|
||||
expect(changedOperations).not.toEqual(
|
||||
expect.arrayContaining([
|
||||
'general.me',
|
||||
'turns.getCommandTable',
|
||||
'board.getAccess',
|
||||
'lobby.info',
|
||||
'world.getMap',
|
||||
'messages.getRecent',
|
||||
@@ -753,6 +898,19 @@ test('realtime read-model events skip clock-only work, merge bursts, patch in pl
|
||||
'turns.reserved.getGeneral',
|
||||
])
|
||||
);
|
||||
const initialBundle = state.dashboardResponses?.find(
|
||||
(entry) => entry.contextKind === 'snapshot' && entry.commandTableKind === 'snapshot'
|
||||
);
|
||||
const realtimeBundle = state.dashboardResponses?.find(
|
||||
(entry) => entry.contextKind === 'patch' && entry.commandTableKind === 'unchanged'
|
||||
);
|
||||
expect(initialBundle?.bytes).toBeGreaterThan(5_000);
|
||||
expect(realtimeBundle).toMatchObject({
|
||||
contextKind: 'patch',
|
||||
commandTableKind: 'unchanged',
|
||||
boardAccessKind: 'unchanged',
|
||||
});
|
||||
expect(realtimeBundle?.bytes).toBeLessThan(1_000);
|
||||
|
||||
const operationsBeforeSurvey = state.operations.length;
|
||||
await page.evaluate(() => {
|
||||
@@ -812,8 +970,10 @@ test('realtime read-model events skip clock-only work, merge bursts, patch in pl
|
||||
expect(profile.cityMounted).toBe(true);
|
||||
expect(profile.generalMutations).toBeGreaterThan(0);
|
||||
expect(profile.cityMutations).toBe(0);
|
||||
expect(profile.vueMeasures.some((name) => name.includes('GeneralBasicCard'))).toBe(true);
|
||||
expect(profile.vueMeasures.some((name) => name.includes('CityBasicCard'))).toBe(false);
|
||||
if (!productionBundle) {
|
||||
expect(profile.vueMeasures.some((name) => name.includes('GeneralBasicCard'))).toBe(true);
|
||||
expect(profile.vueMeasures.some((name) => name.includes('CityBasicCard'))).toBe(false);
|
||||
}
|
||||
if (autoRefreshArtifactRoot) {
|
||||
await Promise.all([
|
||||
page.screenshot({ path: resolve(autoRefreshArtifactRoot, 'auto-refresh-complete.png'), fullPage: true }),
|
||||
@@ -823,6 +983,15 @@ test('realtime read-model events skip clock-only work, merge bursts, patch in pl
|
||||
{
|
||||
emittedTurnEvents: 100,
|
||||
selectiveGeneralRefreshes: state.generalMeCalls - callsBeforeRefresh,
|
||||
responseBytes: {
|
||||
initialSnapshot: initialBundle?.bytes ?? null,
|
||||
realtimeDelta: realtimeBundle?.bytes ?? null,
|
||||
reductionPercent:
|
||||
initialBundle && realtimeBundle
|
||||
? Number(((1 - realtimeBundle.bytes / initialBundle.bytes) * 100).toFixed(2))
|
||||
: null,
|
||||
},
|
||||
responseKinds: realtimeBundle ?? null,
|
||||
inFlightSkeletons: { general: 0, city: 0 },
|
||||
...profile,
|
||||
},
|
||||
|
||||
@@ -9,6 +9,10 @@ const gameProfile = process.env.PLAYWRIGHT_GAME_PROFILE ?? 'che:default';
|
||||
const baseURL = `http://127.0.0.1:${port}${basePath}/`;
|
||||
const gameApiUrl = process.env.PLAYWRIGHT_GAME_API_URL ?? `${basePath}/api/trpc`;
|
||||
const gatewayWebUrl = process.env.PLAYWRIGHT_GATEWAY_WEB_URL ?? '/gateway/';
|
||||
const useProductionBundle = process.env.PLAYWRIGHT_FRONTEND_MODE === 'production';
|
||||
const frontendEnv =
|
||||
`VITE_APP_BASE_PATH=${basePath} VITE_GAME_API_URL=${gameApiUrl} ` +
|
||||
`VITE_GAME_PROFILE=${gameProfile} VITE_GATEWAY_WEB_URL=${gatewayWebUrl}`;
|
||||
|
||||
export default defineConfig({
|
||||
testDir: '.',
|
||||
@@ -55,7 +59,9 @@ export default defineConfig({
|
||||
screenshot: 'only-on-failure',
|
||||
},
|
||||
webServer: {
|
||||
command: `VITE_APP_BASE_PATH=${basePath} VITE_GAME_API_URL=${gameApiUrl} VITE_GAME_PROFILE=${gameProfile} VITE_GATEWAY_WEB_URL=${gatewayWebUrl} pnpm --filter @sammo-ts/game-frontend dev --host 127.0.0.1 --port ${port}`,
|
||||
command: useProductionBundle
|
||||
? `${frontendEnv} pnpm --filter @sammo-ts/game-frontend build && ${frontendEnv} pnpm --filter @sammo-ts/game-frontend preview --host 127.0.0.1 --port ${port}`
|
||||
: `${frontendEnv} pnpm --filter @sammo-ts/game-frontend dev --host 127.0.0.1 --port ${port}`,
|
||||
cwd: repositoryRoot,
|
||||
url: baseURL,
|
||||
reuseExistingServer: false,
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { computed, ref, toRaw, watch } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { MESSAGE_MAILBOX_NATIONAL_BASE, MESSAGE_MAILBOX_PUBLIC, type MessageType } from '@sammo-ts/logic';
|
||||
import type { RealtimeEvent, RealtimeReadModelChanges } from '@sammo-ts/common';
|
||||
import {
|
||||
applyReadModelDelta,
|
||||
ReadModelDeltaMismatchError,
|
||||
type RealtimeEvent,
|
||||
type RealtimeReadModelChanges,
|
||||
} from '@sammo-ts/common';
|
||||
import { trpc } from '../utils/trpc';
|
||||
import { useMapViewerStore } from './mapViewer';
|
||||
import { useSessionStore } from './session';
|
||||
@@ -36,7 +41,17 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
||||
type ReservedTurnView = Awaited<ReturnType<typeof trpc.turns.reserved.getGeneral.query>>['turns'][number];
|
||||
type RecentRecord = Awaited<ReturnType<typeof trpc.general.getRecentRecords.query>>['global'][number];
|
||||
type FrontStatus = Awaited<ReturnType<typeof trpc.general.getFrontStatus.query>>;
|
||||
type ContextBundleDelta = Awaited<ReturnType<typeof trpc.dashboard.getContextBundleDelta.query>>;
|
||||
type ContextBundleInclude = {
|
||||
context: boolean;
|
||||
commandTable: boolean;
|
||||
boardAccess: boolean;
|
||||
};
|
||||
type DashboardReadModelPatch = {
|
||||
contextSnapshot?: GeneralContext;
|
||||
contextRevision?: string | null;
|
||||
commandTableRevision?: string | null;
|
||||
boardAccessRevision?: string | null;
|
||||
general?: PresentGeneralContext['general'] | null;
|
||||
city?: PresentGeneralContext['city'] | null;
|
||||
nation?: PresentGeneralContext['nation'] | null;
|
||||
@@ -87,6 +102,10 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
||||
let lastWorldHistoryId = 0;
|
||||
let recordGeneralId: number | null = null;
|
||||
let initialized = false;
|
||||
let contextSnapshot: GeneralContext | undefined;
|
||||
let contextRevision: string | null = null;
|
||||
let commandTableRevision: string | null = null;
|
||||
let boardAccessRevision: string | null = null;
|
||||
|
||||
const messageDraftText = ref('');
|
||||
const targetMailbox = ref<number>(MESSAGE_MAILBOX_PUBLIC);
|
||||
@@ -298,14 +317,37 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
||||
};
|
||||
|
||||
const applyDashboardPatch = (patch: DashboardReadModelPatch) => {
|
||||
if (patch.general === null) {
|
||||
if (patch.contextSnapshot === null) {
|
||||
contextSnapshot = null;
|
||||
general.value = null;
|
||||
city.value = null;
|
||||
nation.value = null;
|
||||
commandTable.value = null;
|
||||
boardAccess.value = null;
|
||||
reservedGeneralTurns.value = null;
|
||||
reservedGeneralRevision.value = 0;
|
||||
boardAccess.value = null;
|
||||
resetRecentRecords(null);
|
||||
commandTableRevision = null;
|
||||
boardAccessRevision = null;
|
||||
} else if (patch.contextSnapshot !== undefined) {
|
||||
contextSnapshot = patch.contextSnapshot;
|
||||
general.value = structurallyShare(general.value, patch.contextSnapshot.general);
|
||||
city.value = structurallyShare(city.value, patch.contextSnapshot.city);
|
||||
nation.value = structurallyShare(nation.value, patch.contextSnapshot.nation);
|
||||
}
|
||||
if (patch.general === null) {
|
||||
contextSnapshot = null;
|
||||
general.value = null;
|
||||
city.value = null;
|
||||
nation.value = null;
|
||||
commandTable.value = null;
|
||||
boardAccess.value = null;
|
||||
reservedGeneralTurns.value = null;
|
||||
reservedGeneralRevision.value = 0;
|
||||
resetRecentRecords(null);
|
||||
contextRevision = null;
|
||||
commandTableRevision = null;
|
||||
boardAccessRevision = null;
|
||||
} else if (patch.general !== undefined) {
|
||||
general.value = structurallyShare(general.value, patch.general);
|
||||
}
|
||||
@@ -350,10 +392,17 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
||||
} else if (patch.frontStatus !== undefined) {
|
||||
updateFrontStatus(patch.frontStatus);
|
||||
}
|
||||
if (patch.contextRevision !== undefined) contextRevision = patch.contextRevision;
|
||||
if (patch.commandTableRevision !== undefined) commandTableRevision = patch.commandTableRevision;
|
||||
if (patch.boardAccessRevision !== undefined) boardAccessRevision = patch.boardAccessRevision;
|
||||
};
|
||||
|
||||
const currentDashboardPatch = (): DashboardReadModelPatch => {
|
||||
const patch: DashboardReadModelPatch = {};
|
||||
patch.contextSnapshot = toRaw(contextSnapshot);
|
||||
patch.contextRevision = contextRevision;
|
||||
patch.commandTableRevision = commandTableRevision;
|
||||
patch.boardAccessRevision = boardAccessRevision;
|
||||
patch.general = toRaw(general.value);
|
||||
patch.city = toRaw(city.value);
|
||||
patch.nation = toRaw(nation.value);
|
||||
@@ -373,6 +422,64 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
||||
return patch;
|
||||
};
|
||||
|
||||
const resolveContextBundlePatch = (bundle: ContextBundleDelta): DashboardReadModelPatch => {
|
||||
const patch: DashboardReadModelPatch = {};
|
||||
|
||||
if (bundle.context) {
|
||||
const applied = applyReadModelDelta(contextSnapshot, contextRevision, bundle.context);
|
||||
patch.contextRevision = applied.revision;
|
||||
if (bundle.context.kind !== 'unchanged') {
|
||||
patch.contextSnapshot = applied.data;
|
||||
}
|
||||
}
|
||||
if (bundle.commandTable) {
|
||||
const current = commandTable.value === null ? undefined : toRaw(commandTable.value);
|
||||
const applied = applyReadModelDelta(current, commandTableRevision, bundle.commandTable);
|
||||
patch.commandTableRevision = applied.revision;
|
||||
if (bundle.commandTable.kind !== 'unchanged') {
|
||||
patch.commandTable = applied.data;
|
||||
}
|
||||
}
|
||||
if (bundle.boardAccess) {
|
||||
const current = boardAccess.value === null ? undefined : toRaw(boardAccess.value);
|
||||
const applied = applyReadModelDelta(current, boardAccessRevision, bundle.boardAccess);
|
||||
patch.boardAccessRevision = applied.revision;
|
||||
if (bundle.boardAccess.kind !== 'unchanged') {
|
||||
patch.boardAccess = applied.data;
|
||||
}
|
||||
}
|
||||
|
||||
return patch;
|
||||
};
|
||||
|
||||
const fetchContextBundlePatch = async (
|
||||
include: ContextBundleInclude,
|
||||
forceSnapshot = false
|
||||
): Promise<DashboardReadModelPatch> => {
|
||||
const request = (force: boolean) =>
|
||||
trpc.dashboard.getContextBundleDelta.query({
|
||||
include,
|
||||
known: force
|
||||
? undefined
|
||||
: {
|
||||
...(contextRevision ? { context: contextRevision } : {}),
|
||||
...(commandTableRevision ? { commandTable: commandTableRevision } : {}),
|
||||
...(boardAccessRevision ? { boardAccess: boardAccessRevision } : {}),
|
||||
},
|
||||
forceSnapshot: force || undefined,
|
||||
});
|
||||
|
||||
const bundle = await request(forceSnapshot);
|
||||
try {
|
||||
return resolveContextBundlePatch(bundle);
|
||||
} catch (error) {
|
||||
if (forceSnapshot || !(error instanceof ReadModelDeltaMismatchError)) {
|
||||
throw error;
|
||||
}
|
||||
return resolveContextBundlePatch(await request(true));
|
||||
}
|
||||
};
|
||||
|
||||
const refreshMainData = async () => {
|
||||
const isInitialLoad = !initialized;
|
||||
if (isInitialLoad) {
|
||||
@@ -385,16 +492,14 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
||||
frontStatusError.value = null;
|
||||
|
||||
try {
|
||||
const context = await trpc.general.me.query();
|
||||
const contextPatch = await fetchContextBundlePatch(
|
||||
{ context: true, commandTable: true, boardAccess: true },
|
||||
true
|
||||
);
|
||||
applyDashboardPatch(contextPatch);
|
||||
const context = contextSnapshot;
|
||||
|
||||
if (!context) {
|
||||
general.value = null;
|
||||
city.value = null;
|
||||
nation.value = null;
|
||||
reservedGeneralTurns.value = null;
|
||||
reservedGeneralRevision.value = 0;
|
||||
boardAccess.value = null;
|
||||
resetRecentRecords(null);
|
||||
initialized = true;
|
||||
return;
|
||||
}
|
||||
@@ -418,29 +523,17 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
||||
frontStatusError.value = resolveErrorMessage(err);
|
||||
return null;
|
||||
});
|
||||
const [
|
||||
layout,
|
||||
lobby,
|
||||
map,
|
||||
commands,
|
||||
messageData,
|
||||
contacts,
|
||||
access,
|
||||
generalTurns,
|
||||
records,
|
||||
nextFrontStatus,
|
||||
] = await Promise.all([
|
||||
layoutPromise,
|
||||
trpc.lobby.info.query(),
|
||||
trpc.world.getMap.query({ generalId: id, showMe: true, useCache: true }),
|
||||
trpc.turns.getCommandTable.query({ generalId: id }),
|
||||
trpc.messages.getRecent.query({ generalId: id }),
|
||||
trpc.messages.getContacts.query({ generalId: id }),
|
||||
trpc.board.getAccess.query(),
|
||||
generalTurnsPromise,
|
||||
recordsPromise,
|
||||
frontStatusPromise,
|
||||
]);
|
||||
const [layout, lobby, map, messageData, contacts, generalTurns, records, nextFrontStatus] =
|
||||
await Promise.all([
|
||||
layoutPromise,
|
||||
trpc.lobby.info.query(),
|
||||
trpc.world.getMap.query({ generalId: id, showMe: true, useCache: true }),
|
||||
trpc.messages.getRecent.query({ generalId: id }),
|
||||
trpc.messages.getContacts.query({ generalId: id }),
|
||||
generalTurnsPromise,
|
||||
recordsPromise,
|
||||
frontStatusPromise,
|
||||
]);
|
||||
|
||||
general.value = structurallyShare(general.value, context.general);
|
||||
city.value = structurallyShare(city.value, context.city);
|
||||
@@ -448,10 +541,8 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
||||
mapLayout.value = structurallyShare(mapLayout.value, layout);
|
||||
lobbyInfo.value = structurallyShare(lobbyInfo.value, lobby);
|
||||
worldMap.value = structurallyShare(worldMap.value, map);
|
||||
commandTable.value = structurallyShare(commandTable.value, commands);
|
||||
messages.value = structurallyShare(messages.value, messageData);
|
||||
messageContacts.value = structurallyShare(messageContacts.value, contacts);
|
||||
boardAccess.value = structurallyShare(boardAccess.value, access);
|
||||
reservedGeneralTurns.value = structurallyShare<unknown>(
|
||||
reservedGeneralTurns.value,
|
||||
generalTurns.turns
|
||||
@@ -517,20 +608,21 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
||||
if (plan.records) recordsError.value = null;
|
||||
if (plan.frontStatus) frontStatusError.value = null;
|
||||
try {
|
||||
const contextPromise = plan.context
|
||||
? trpc.general.me.query()
|
||||
: Promise.resolve(undefined as GeneralContext | undefined);
|
||||
const contextBundlePromise =
|
||||
plan.context || plan.commands || plan.boardAccess
|
||||
? fetchContextBundlePatch({
|
||||
context: plan.context,
|
||||
commandTable: plan.commands,
|
||||
boardAccess: plan.boardAccess,
|
||||
})
|
||||
: Promise.resolve(undefined);
|
||||
const lobbyPromise = plan.lobby ? trpc.lobby.info.query() : Promise.resolve(undefined);
|
||||
const mapPromise = plan.map
|
||||
? trpc.world.getMap.query({ generalId: id, showMe: true, useCache: true })
|
||||
: Promise.resolve(undefined);
|
||||
const commandsPromise = plan.commands
|
||||
? trpc.turns.getCommandTable.query({ generalId: id })
|
||||
: Promise.resolve(undefined);
|
||||
const contactsPromise = plan.contacts
|
||||
? trpc.messages.getContacts.query({ generalId: id })
|
||||
: Promise.resolve(undefined);
|
||||
const boardPromise = plan.boardAccess ? trpc.board.getAccess.query() : Promise.resolve(undefined);
|
||||
const reservedPromise = plan.reservedTurns
|
||||
? trpc.turns.reserved.getGeneral.query({ generalId: id })
|
||||
: Promise.resolve(undefined);
|
||||
@@ -549,32 +641,20 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
||||
})
|
||||
: Promise.resolve(undefined);
|
||||
|
||||
const [context, lobby, map, commands, contacts, access, generalTurns, records, nextFrontStatus] =
|
||||
await Promise.all([
|
||||
contextPromise,
|
||||
lobbyPromise,
|
||||
mapPromise,
|
||||
commandsPromise,
|
||||
contactsPromise,
|
||||
boardPromise,
|
||||
reservedPromise,
|
||||
recordsPromise,
|
||||
frontPromise,
|
||||
]);
|
||||
const [contextPatch, lobby, map, contacts, generalTurns, records, nextFrontStatus] = await Promise.all([
|
||||
contextBundlePromise,
|
||||
lobbyPromise,
|
||||
mapPromise,
|
||||
contactsPromise,
|
||||
reservedPromise,
|
||||
recordsPromise,
|
||||
frontPromise,
|
||||
]);
|
||||
|
||||
const patch: DashboardReadModelPatch = {};
|
||||
if (context === null) {
|
||||
patch.general = null;
|
||||
} else if (context !== undefined) {
|
||||
patch.general = context.general;
|
||||
patch.city = context.city;
|
||||
patch.nation = context.nation;
|
||||
}
|
||||
const patch: DashboardReadModelPatch = contextPatch ? { ...contextPatch } : {};
|
||||
if (lobby !== undefined) patch.lobbyInfo = lobby;
|
||||
if (map !== undefined) patch.worldMap = map;
|
||||
if (commands !== undefined) patch.commandTable = commands;
|
||||
if (contacts !== undefined) patch.messageContacts = contacts;
|
||||
if (access !== undefined) patch.boardAccess = access;
|
||||
if (generalTurns !== undefined) {
|
||||
patch.reservedGeneralTurns = generalTurns.turns;
|
||||
patch.reservedGeneralRevision = generalTurns.revision;
|
||||
|
||||
Reference in New Issue
Block a user