perf(client): defer chat persistence dependencies

This commit is contained in:
2026-07-15 17:58:06 +08:00
parent 86ffbbcdbc
commit 05ca15be48
15 changed files with 115 additions and 81 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
import type { UiMessage } from "@/data/dto/chat";
import { resolveChatCacheOwnerKey } from "@/data/repositories/chat_cache_identity";
import { getChatRepository } from "@/data/repositories/chat_repository";
import { loadChatRepository } from "@/data/repositories/chat_repository_loader";
import { Logger } from "@/utils/logger";
import { Result } from "@/utils/result";
import { todayString } from "@/utils/date";
@@ -47,7 +47,7 @@ export async function resolveHistoryCacheIdentity(): Promise<string | null> {
export async function readLocalHistorySnapshot(
cacheIdentity: string | null,
): Promise<LocalHistorySnapshotOutput> {
const chatRepo = getChatRepository();
const chatRepo = await loadChatRepository();
const greetingMessage = createGreetingMessage();
const localResult = cacheIdentity
@@ -77,7 +77,7 @@ export async function syncNetworkHistory(
localCount: number,
cacheIdentity: string | null,
): Promise<NetworkHistorySyncOutput | null> {
const chatRepo = getChatRepository();
const chatRepo = await loadChatRepository();
const greetingMessage = createGreetingMessage();
const networkResult = await chatRepo.getHistory(CHAT_HISTORY_LIMIT, 0);
+2 -2
View File
@@ -3,7 +3,7 @@ import { fromCallback, fromPromise } from "xstate";
import { ExceptionHandler } from "@/core/errors";
import { MessageQueue } from "@/core/net/message-queue";
import type { ChatSendResponse } from "@/data/dto/chat";
import { getChatRepository } from "@/data/repositories/chat_repository";
import { loadChatRepository } from "@/data/repositories/chat_repository_loader";
import { resolveChatCacheOwnerKey } from "@/data/repositories/chat_cache_identity";
import { Logger } from "@/utils/logger";
import { Result } from "@/utils/result";
@@ -66,7 +66,7 @@ async function sendMessageViaHttp(content: string): Promise<{
response: ChatSendResponse;
reply: UiMessage | null;
}> {
const chatRepo = getChatRepository();
const chatRepo = await loadChatRepository();
const cacheIdentityResult = await resolveChatCacheOwnerKey();
const result = await chatRepo.sendMessage(content);
if (Result.isErr(result)) {
+3 -3
View File
@@ -1,6 +1,6 @@
import { fromPromise } from "xstate";
import { getChatRepository } from "@/data/repositories/chat_repository";
import { loadChatRepository } from "@/data/repositories/chat_repository_loader";
import { resolveChatCacheOwnerKey } from "@/data/repositories/chat_cache_identity";
import { Logger } from "@/utils/logger";
import { Result } from "@/utils/result";
@@ -23,7 +23,7 @@ export interface UnlockHistoryOutput {
}
export const unlockHistoryActor = fromPromise<UnlockHistoryOutput>(async () => {
const chatRepo = getChatRepository();
const chatRepo = await loadChatRepository();
const unlockResult = await chatRepo.unlockHistory();
if (Result.isErr(unlockResult)) {
log.error("[chat-machine] unlockHistoryActor failed", {
@@ -45,7 +45,7 @@ export const unlockMessageActor = fromPromise<
UnlockMessageOutput,
UnlockMessageRequest
>(async ({ input }) => {
const chatRepo = getChatRepository();
const chatRepo = await loadChatRepository();
const cacheIdentityResult = await resolveChatCacheOwnerKey();
const unlockResult = await chatRepo.unlockPrivateMessage({
...(input.messageId ? { messageId: input.messageId } : {}),
@@ -0,0 +1,47 @@
"use client";
/** Payment → Chat bridge, mounted only while the chat route is active. */
import { useEffect, useRef } from "react";
import { shallowEqual } from "@xstate/react";
import { hasPendingChatUnlock } from "@/lib/navigation/chat_unlock_session";
import { useChatDispatch } from "@/stores/chat/chat-context";
import { usePaymentSelector } from "@/stores/payment/payment-context";
export function ChatPaymentSuccessSync() {
const paymentState = usePaymentSelector(
(state) => ({
currentOrderId: state.context.currentOrderId,
isPaid: state.matches("paid"),
orderStatus: state.context.orderStatus,
}),
shallowEqual,
);
const chatDispatch = useChatDispatch();
const lastPaidKeyRef = useRef<string | null>(null);
useEffect(() => {
if (!paymentState.isPaid || !paymentState.currentOrderId) return;
const paidKey = `${paymentState.currentOrderId}:${paymentState.orderStatus}`;
if (lastPaidKeyRef.current === paidKey) return;
lastPaidKeyRef.current = paidKey;
let cancelled = false;
void (async () => {
if (await hasPendingChatUnlock()) return;
if (!cancelled) chatDispatch({ type: "ChatPaymentSucceeded" });
})();
return () => {
cancelled = true;
};
}, [
chatDispatch,
paymentState.currentOrderId,
paymentState.isPaid,
paymentState.orderStatus,
]);
return null;
}
+1
View File
@@ -1,3 +1,4 @@
export * from "./chat-auth-sync";
export * from "./chat-payment-success-sync";
export * from "./payment-success-sync";
export * from "./user-auth-sync";
-41
View File
@@ -8,9 +8,7 @@
import { useEffect, useRef } from "react";
import { shallowEqual } from "@xstate/react";
import { useChatDispatch } from "@/stores/chat/chat-context";
import { useUserDispatch } from "@/stores/user/user-context";
import { hasPendingChatUnlock } from "@/lib/navigation/chat_unlock_session";
import {
usePaymentDispatch,
usePaymentSelector,
@@ -54,42 +52,3 @@ export function PaymentSuccessSync() {
return null;
}
/** Payment → Chat bridge, mounted only while the chat route is active. */
export function ChatPaymentSuccessSync() {
const paymentState = usePaymentSelector(
(state) => ({
currentOrderId: state.context.currentOrderId,
isPaid: state.matches("paid"),
orderStatus: state.context.orderStatus,
}),
shallowEqual,
);
const chatDispatch = useChatDispatch();
const lastPaidKeyRef = useRef<string | null>(null);
useEffect(() => {
if (!paymentState.isPaid || !paymentState.currentOrderId) return;
const paidKey = `${paymentState.currentOrderId}:${paymentState.orderStatus}`;
if (lastPaidKeyRef.current === paidKey) return;
lastPaidKeyRef.current = paidKey;
let cancelled = false;
void (async () => {
if (await hasPendingChatUnlock()) return;
if (!cancelled) chatDispatch({ type: "ChatPaymentSucceeded" });
})();
return () => {
cancelled = true;
};
}, [
chatDispatch,
paymentState.currentOrderId,
paymentState.isPaid,
paymentState.orderStatus,
]);
return null;
}