Skip to content

Commit 7c92041

Browse files
committed
refactor(session): break provider<->hook type cycle, fail-fast session query
Address review feedback: - Move the AppSession type to lib/auth/session-response.ts (the module that produces it) so useSessionQuery and SessionProvider both import it from there, eliminating the provider <-> query-hook import cycle. - Add retry: false to useSessionQuery, restoring the prior fail-fast contract (the global QueryClient default is retry: 1; an auth failure should surface immediately rather than retry a request that won't succeed). - Return null (not the fetched value) from refreshAfterUpgrade's cancelled branch to make the cancellation contract explicit.
1 parent 9ccde3a commit 7c92041

4 files changed

Lines changed: 40 additions & 25 deletions

File tree

apps/sim/app/_shell/providers/session-provider.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ vi.mock('posthog-js', () => ({
3232
},
3333
}))
3434

35+
import type { AppSession } from '@/lib/auth/session-response'
3536
import {
36-
type AppSession,
3737
SessionContext,
3838
type SessionHookResult,
3939
SessionProvider,

apps/sim/app/_shell/providers/session-provider.tsx

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,12 @@ import { useQueryClient } from '@tanstack/react-query'
77
import { requestJson } from '@/lib/api/client/request'
88
import { listCreatorOrganizationsContract } from '@/lib/api/contracts/organizations'
99
import { client } from '@/lib/auth/auth-client'
10-
import { extractSessionDataFromAuthClientResult } from '@/lib/auth/session-response'
10+
import {
11+
type AppSession,
12+
extractSessionDataFromAuthClientResult,
13+
} from '@/lib/auth/session-response'
1114
import { sessionKeys, useSessionQuery } from '@/hooks/queries/session'
1215

13-
export type AppSession = {
14-
user: {
15-
id: string
16-
email: string
17-
emailVerified?: boolean
18-
name?: string | null
19-
image?: string | null
20-
role?: string
21-
createdAt?: Date
22-
updatedAt?: Date
23-
} | null
24-
session?: {
25-
id?: string
26-
userId?: string
27-
activeOrganizationId?: string
28-
impersonatedBy?: string | null
29-
}
30-
} | null
31-
3216
export type SessionHookResult = {
3317
data: AppSession
3418
isPending: boolean
@@ -65,7 +49,7 @@ export function SessionProvider({ children }: { children: React.ReactNode }) {
6549
const res = await client.getSession({ query: { disableCookieCache: true } })
6650
const fresh = extractSessionDataFromAuthClientResult(res) as AppSession
6751

68-
if (isCancelled) return
52+
if (isCancelled) return null
6953

7054
await queryClient.cancelQueries({ queryKey: sessionKeys.detail() })
7155
queryClient.setQueryData(sessionKeys.detail(), fresh)
@@ -75,7 +59,7 @@ export function SessionProvider({ children }: { children: React.ReactNode }) {
7559
const initializeSession = async () => {
7660
let session: AppSession = null
7761
try {
78-
session = (await refreshAfterUpgrade()) ?? null
62+
session = await refreshAfterUpgrade()
7963
} catch (e) {
8064
logger.warn('Failed to refresh session after subscription upgrade', { error: e })
8165
}

apps/sim/hooks/queries/session.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { useQuery } from '@tanstack/react-query'
22
import { client } from '@/lib/auth/auth-client'
3-
import { extractSessionDataFromAuthClientResult } from '@/lib/auth/session-response'
4-
import type { AppSession } from '@/app/_shell/providers/session-provider'
3+
import {
4+
type AppSession,
5+
extractSessionDataFromAuthClientResult,
6+
} from '@/lib/auth/session-response'
57

68
export const sessionKeys = {
79
all: ['session'] as const,
@@ -18,11 +20,16 @@ async function fetchSession(signal?: AbortSignal): Promise<AppSession> {
1820
*
1921
* This is the Better Auth client SDK (not a same-origin `requestJson` contract),
2022
* so a plain `useQuery` is correct — there is no boundary contract to bind.
23+
*
24+
* `retry: false` preserves the prior fail-fast contract: an auth failure (expired
25+
* token, startup network partition) surfaces immediately rather than retrying a
26+
* request that won't succeed.
2127
*/
2228
export function useSessionQuery() {
2329
return useQuery({
2430
queryKey: sessionKeys.detail(),
2531
queryFn: ({ signal }) => fetchSession(signal),
2632
staleTime: 5 * 60 * 1000,
33+
retry: false,
2734
})
2835
}

apps/sim/lib/auth/session-response.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,27 @@
1+
/**
2+
* The app-facing session shape derived from the Better Auth client response.
3+
* Lives here (the module that produces it) so both the `useSessionQuery` hook
4+
* and the `SessionProvider` can import it without a provider ↔ hook import cycle.
5+
*/
6+
export type AppSession = {
7+
user: {
8+
id: string
9+
email: string
10+
emailVerified?: boolean
11+
name?: string | null
12+
image?: string | null
13+
role?: string
14+
createdAt?: Date
15+
updatedAt?: Date
16+
} | null
17+
session?: {
18+
id?: string
19+
userId?: string
20+
activeOrganizationId?: string
21+
impersonatedBy?: string | null
22+
}
23+
} | null
24+
125
export function extractSessionDataFromAuthClientResult(result: unknown): unknown | null {
226
if (!result || typeof result !== 'object') {
327
return null

0 commit comments

Comments
 (0)