feat(portal-shell): implement portal-shell with apollo-router integration
M8: portal-shell unified frontend shell (Modular Monolith + micro-kernel). - Apollo Client -> apollo-router (port 4010, RSC prefetch) - 5 layouts: classic/focus/split/triple/canvas - Registry + PluginLoader (dynamic import ssr:false) - 3-layer props merge, Zustand PluginStore - 4 widgets: grades/notification-bell/user-menu/class-selector - config-service: new pluginConfig GraphQL resolver - apollo-router: CORS + header propagation for portal-shell - docker-compose.yml: portal-shell service block
This commit is contained in:
75
apps/portal-shell/src/lib/apollo-client.ts
Normal file
75
apps/portal-shell/src/lib/apollo-client.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Apollo Client(v2.1 M8 验收点)
|
||||
*
|
||||
* 所有 portal-shell 查询走 apollo-router(GraphQL 联邦入口):
|
||||
* portal-shell → apollo-router :3000/graphql → 各子图(iam/core-edu/content/msg/data-ana/ai/config-service)
|
||||
*
|
||||
* 双端使用:
|
||||
* - 服务端(RSC):createApolloClient() 每次请求新建实例,ssrMode=true
|
||||
* - 客户端:getApolloClient() 单例,复用 InMemoryCache
|
||||
*
|
||||
* 关联:portal-shell spec §5.5 RSC 预取、§5.6 统一 Hook、M8 验收标准
|
||||
*/
|
||||
import { ApolloClient, InMemoryCache, HttpLink, from } from "@apollo/client";
|
||||
import { setContext } from "@apollo/client/link/context";
|
||||
|
||||
const APOLLO_ROUTER_URL =
|
||||
process.env.NEXT_PUBLIC_APOLLO_ROUTER_URL ||
|
||||
process.env.APOLLO_ROUTER_URL ||
|
||||
"http://localhost:3000/graphql";
|
||||
|
||||
/**
|
||||
* 创建 Apollo Client 实例。
|
||||
*
|
||||
* @param getAuthToken 可选,返回 JWT 用于注入 Authorization 头(客户端从 cookie/localStorage 读取)
|
||||
*/
|
||||
export function createApolloClient(
|
||||
getAuthToken?: () => string | null,
|
||||
): ApolloClient<unknown> {
|
||||
const httpLink = new HttpLink({
|
||||
uri: APOLLO_ROUTER_URL,
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
const authLink = setContext((_, { headers }) => {
|
||||
const token = getAuthToken?.() ?? null;
|
||||
return {
|
||||
headers: {
|
||||
...headers,
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return new ApolloClient({
|
||||
link: from([authLink, httpLink]),
|
||||
cache: new InMemoryCache(),
|
||||
ssrMode: typeof window === "undefined",
|
||||
defaultOptions: {
|
||||
query: {
|
||||
errorPolicy: "all",
|
||||
fetchPolicy: typeof window === "undefined" ? "no-cache" : "cache-first",
|
||||
},
|
||||
watchQuery: {
|
||||
errorPolicy: "all",
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let clientSingleton: ApolloClient<unknown> | null = null;
|
||||
|
||||
/**
|
||||
* 获取客户端 Apollo Client 单例(浏览器侧复用缓存)。
|
||||
*/
|
||||
export function getApolloClient(
|
||||
getAuthToken?: () => string | null,
|
||||
): ApolloClient<unknown> {
|
||||
if (typeof window === "undefined") {
|
||||
return createApolloClient(getAuthToken);
|
||||
}
|
||||
if (!clientSingleton) {
|
||||
clientSingleton = createApolloClient(getAuthToken);
|
||||
}
|
||||
return clientSingleton;
|
||||
}
|
||||
Reference in New Issue
Block a user