返回 DeepSeek-Reasonix
sessionIdentity.ts
根目录 / desktop / frontend / src / lib / sessionIdentity.ts
1 import type { SessionRef } from "./sessionRef";
2
3 export type SessionIdentityRef = Readonly<SessionRef>;
4
5 export type SessionIdentity = Readonly<{
6 session?: SessionIdentityRef | null;
7 sessionPath?: string;
8 sessionGeneration?: number;
9 }>;
10
11 /** Zero is the initial host binding; only absent/invalid generations are unknown. */
12 export function hasSessionGeneration(value: unknown): value is number {
13 return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
14 }
15
16 export type SessionHydrationOptions<Item, SurfacePolicy extends string> = SessionIdentity & Readonly<{
17 skipHistory?: boolean;
18 placeholderItems?: Item[];
19 preserveCachedHistory?: boolean;
20 freshSnapshot?: boolean;
21 sessionRevision?: number;
22 sessionDigest?: string;
23 cancelHydrateGeneration?: number;
24 deferResetUntilHistory?: boolean;
25 surfacePolicy?: SurfacePolicy;
26 recoveryCurrent?: () => boolean;
27 }>;
28
29 export function sessionIdentityFields(identity: SessionIdentity | undefined): SessionIdentity {
30 return {
31 session: identity?.session,
32 sessionPath: identity?.sessionPath,
33 sessionGeneration: identity?.sessionGeneration,
34 };
35 }
36
37 export function sessionIdentityBaseKey(identity: SessionIdentity | undefined): string {
38 const ref = identity?.session;
39 if (ref?.sessionId) return `ref\0${ref.hostId || "local"}\0${ref.sessionId}`;
40 const path = identity?.sessionPath?.trim() || "";
41 return path ? `path\0${path}` : "";
42 }
43
44 /** Navigation adapters expose the canonical route even when tabs omit paths. */
45 export function sessionIdentityRoute(identity: SessionIdentity | undefined): string | undefined {
46 return identity?.session?.sessionId ? `session-id:${identity.session.sessionId}` : identity?.sessionPath;
47 }
48
49 function sameGeneration(target: SessionIdentity, current: SessionIdentity | undefined): boolean {
50 const targetGeneration = target.sessionGeneration;
51 const currentGeneration = current?.sessionGeneration;
52 return targetGeneration == null || currentGeneration == null || targetGeneration === currentGeneration;
53 }
54
55 /** Canonical SessionRef first; legacy path is only the compatibility identity. */
56 export function sameSessionIdentity(
57 target: SessionIdentity | undefined,
58 current: SessionIdentity | undefined,
59 ): boolean {
60 if (!target || !current) return false;
61 const key = sessionIdentityBaseKey(target);
62 return !!(key && key === sessionIdentityBaseKey(current) && sameGeneration(target, current));
63 }
64
65 /** Stable cache/fence key. An empty string means the identity is not yet proven. */
66 export function sessionIdentityStableKey(identity: SessionIdentity | undefined): string {
67 const key = sessionIdentityBaseKey(identity);
68 return key ? `${key}\0${identity?.sessionGeneration ?? 0}` : "";
69 }
70
71 /** True when an in-flight hydrate still addresses the live Session identity. */
72 export function hydrateIdentityCurrent(
73 load: SessionIdentity,
74 current: SessionIdentity | undefined,
75 ): boolean {
76 if (!sessionIdentityBaseKey(load)) {
77 return sameGeneration(load, current);
78 }
79 return sameSessionIdentity(load, current);
80 }
81
81 lines TYPESCRIPT